From a7e1d22a778b2a88b3a48c08d9595cdadea1eb4f Mon Sep 17 00:00:00 2001 From: Thomas Potaire Date: Tue, 19 Aug 2025 22:02:02 -0700 Subject: [PATCH 1/5] moving coderouter to onlook and minimal cleanup --- README.md | 42 +- apps/backend/supabase/config.toml | 2 + apps/coderouter/.env.example | 11 + apps/coderouter/.gitignore | 2 + apps/coderouter/Dockerfile.dev | 14 + apps/coderouter/README.md | 46 + apps/coderouter/biome.json | 5 + apps/coderouter/bunfig.toml | 2 + apps/coderouter/codecov.yml | 6 + apps/coderouter/drizzle.config.ts | 15 + apps/coderouter/package.json | 38 + apps/coderouter/renovate.json | 5 + apps/coderouter/scripts/migrate.ts | 9 + apps/coderouter/site/index.html | 14 + .../src/api/auth/sign/index.test.ts | 0 apps/coderouter/src/api/auth/sign/index.ts | 81 ++ .../src/api/sandbox/create/index.test.ts | 18 + .../src/api/sandbox/create/index.ts | 76 ++ .../src/api/sandbox/file/copy/index.test.ts | 18 + .../src/api/sandbox/file/copy/index.ts | 64 ++ .../src/api/sandbox/file/delete/index.test.ts | 18 + .../src/api/sandbox/file/delete/index.ts | 51 ++ .../api/sandbox/file/download/index.test.ts | 18 + .../src/api/sandbox/file/download/index.ts | 51 ++ .../src/api/sandbox/file/list/index.test.ts | 18 + .../src/api/sandbox/file/list/index.ts | 59 ++ .../src/api/sandbox/file/read/index.test.ts | 18 + .../src/api/sandbox/file/read/index.ts | 53 ++ .../src/api/sandbox/file/rename/index.test.ts | 18 + .../src/api/sandbox/file/rename/index.ts | 55 ++ .../src/api/sandbox/file/stat/index.test.ts | 18 + .../src/api/sandbox/file/stat/index.ts | 53 ++ .../src/api/sandbox/file/write/index.test.ts | 18 + .../src/api/sandbox/file/write/index.ts | 63 ++ .../src/api/sandbox/pause/index.test.ts | 18 + .../coderouter/src/api/sandbox/pause/index.ts | 54 ++ .../src/api/sandbox/resume/index.test.ts | 18 + .../src/api/sandbox/resume/index.ts | 54 ++ .../src/api/sandbox/stop/index.test.ts | 18 + apps/coderouter/src/api/sandbox/stop/index.ts | 54 ++ .../src/api/sandbox/url/index.test.ts | 18 + apps/coderouter/src/api/sandbox/url/index.ts | 54 ++ apps/coderouter/src/db/connection.ts | 37 + apps/coderouter/src/db/schema.ts | 32 + apps/coderouter/src/middleware/auth.test.ts | 244 +++++ apps/coderouter/src/middleware/auth.ts | 23 + .../src/middleware/beforeSandboxCall.ts | 15 + apps/coderouter/src/middleware/client.test.ts | 277 ++++++ apps/coderouter/src/middleware/client.ts | 29 + apps/coderouter/src/middleware/error.ts | 27 + .../src/middleware/requireSandboxId.test.ts | 318 +++++++ .../src/middleware/requireSandboxId.ts | 16 + .../src/provider/definition/index.test.ts | 320 +++++++ .../src/provider/definition/index.ts | 48 + .../provider/definition/sandbox/file/index.ts | 71 ++ .../src/provider/definition/sandbox/index.ts | 66 ++ apps/coderouter/src/provider/e2b/index.ts | 16 + .../provider/e2b/sandbox/file/index.test.ts | 490 ++++++++++ .../src/provider/e2b/sandbox/file/index.ts | 203 +++++ .../src/provider/e2b/sandbox/index.ts | 136 +++ apps/coderouter/src/provider/index.ts | 0 apps/coderouter/src/server.ts | 97 ++ apps/coderouter/src/util/auth.ts | 41 + apps/coderouter/tsconfig.json | 17 + apps/coderouter/typedoc.json | 5 + apps/docker-compose.yaml | 25 + apps/nginx/bin/genkeys.sh | 10 + apps/nginx/conf.d/server.conf | 43 + apps/nginx/ssl/README.md | 1 + apps/nginx/ssl/onlook-internal.crt | 24 + apps/nginx/ssl/onlook-internal.key | 28 + apps/nginx/ssl/openssl.cnf | 22 + apps/web/client/.env.example | 7 +- .../projects/import/local/_context/index.tsx | 22 +- .../src/components/store/editor/code/index.ts | 7 +- .../store/editor/sandbox/session.ts | 25 +- .../store/editor/sandbox/terminal.ts | 18 +- apps/web/client/src/env.ts | 10 + .../src/server/api/routers/project/sandbox.ts | 65 +- .../api/routers/publish/helpers/fork.ts | 28 +- apps/web/template/.gitignore | 1 + apps/web/template/README.md | 5 + apps/web/template/e2b.Dockerfile | 22 + apps/web/template/e2b.toml.example | 18 + bun.lock | 177 +++- .../docs/contributing/developers/appendix.mdx | 3 +- package.json | 2 +- packages/code-provider/openapitools.json | 7 + packages/code-provider/package.json | 6 +- packages/code-provider/src/index.ts | 9 + packages/code-provider/src/providers.ts | 1 + .../codegen/.openapi-generator-ignore | 23 + .../codegen/.openapi-generator/FILES | 25 + .../codegen/.openapi-generator/VERSION | 1 + .../coderouter/codegen/apis/DefaultApi.ts | 854 ++++++++++++++++++ .../coderouter/codegen/apis/index.ts | 3 + .../src/providers/coderouter/codegen/index.ts | 5 + .../CoderouterApiAuthSignPost200Response.ts | 75 ++ .../CoderouterApiAuthSignPost401Response.ts | 75 ++ .../CoderouterApiAuthSignPostRequest.ts | 82 ++ ...derouterApiSandboxCreatePost200Response.ts | 75 ++ .../CoderouterApiSandboxCreatePostRequest.ts | 84 ++ ...CoderouterApiSandboxFileCopyPostRequest.ts | 102 +++ ...derouterApiSandboxFileDeletePostRequest.ts | 75 ++ ...routerApiSandboxFileDownloadPostRequest.ts | 75 ++ ...routerApiSandboxFileListPost200Response.ts | 87 ++ ...andboxFileListPost200ResponseFilesInner.ts | 103 +++ ...CoderouterApiSandboxFileListPostRequest.ts | 75 ++ ...routerApiSandboxFileReadPost200Response.ts | 75 ++ ...CoderouterApiSandboxFileReadPostRequest.ts | 75 ++ ...derouterApiSandboxFileRenamePostRequest.ts | 84 ++ ...routerApiSandboxFileStatPost200Response.ts | 85 ++ ...CoderouterApiSandboxFileStatPostRequest.ts | 75 ++ ...oderouterApiSandboxFileWritePostRequest.ts | 87 ++ ...piSandboxFileWritePostRequestFilesInner.ts | 93 ++ .../CoderouterApiSandboxUrlPost200Response.ts | 75 ++ .../coderouter/codegen/models/index.ts | 21 + .../providers/coderouter/codegen/runtime.ts | 497 ++++++++++ .../src/providers/coderouter/index.ts | 485 ++++++++++ .../src/providers/codesandbox/index.ts | 10 +- .../src/providers/nodefs/index.ts | 10 +- packages/code-provider/src/types.ts | 28 +- packages/scripts/src/api-keys.ts | 10 +- 123 files changed, 7802 insertions(+), 108 deletions(-) create mode 100644 apps/coderouter/.env.example create mode 100644 apps/coderouter/.gitignore create mode 100644 apps/coderouter/Dockerfile.dev create mode 100644 apps/coderouter/README.md create mode 100644 apps/coderouter/biome.json create mode 100644 apps/coderouter/bunfig.toml create mode 100644 apps/coderouter/codecov.yml create mode 100644 apps/coderouter/drizzle.config.ts create mode 100644 apps/coderouter/package.json create mode 100644 apps/coderouter/renovate.json create mode 100644 apps/coderouter/scripts/migrate.ts create mode 100644 apps/coderouter/site/index.html create mode 100644 apps/coderouter/src/api/auth/sign/index.test.ts create mode 100644 apps/coderouter/src/api/auth/sign/index.ts create mode 100644 apps/coderouter/src/api/sandbox/create/index.test.ts create mode 100644 apps/coderouter/src/api/sandbox/create/index.ts create mode 100644 apps/coderouter/src/api/sandbox/file/copy/index.test.ts create mode 100644 apps/coderouter/src/api/sandbox/file/copy/index.ts create mode 100644 apps/coderouter/src/api/sandbox/file/delete/index.test.ts create mode 100644 apps/coderouter/src/api/sandbox/file/delete/index.ts create mode 100644 apps/coderouter/src/api/sandbox/file/download/index.test.ts create mode 100644 apps/coderouter/src/api/sandbox/file/download/index.ts create mode 100644 apps/coderouter/src/api/sandbox/file/list/index.test.ts create mode 100644 apps/coderouter/src/api/sandbox/file/list/index.ts create mode 100644 apps/coderouter/src/api/sandbox/file/read/index.test.ts create mode 100644 apps/coderouter/src/api/sandbox/file/read/index.ts create mode 100644 apps/coderouter/src/api/sandbox/file/rename/index.test.ts create mode 100644 apps/coderouter/src/api/sandbox/file/rename/index.ts create mode 100644 apps/coderouter/src/api/sandbox/file/stat/index.test.ts create mode 100644 apps/coderouter/src/api/sandbox/file/stat/index.ts create mode 100644 apps/coderouter/src/api/sandbox/file/write/index.test.ts create mode 100644 apps/coderouter/src/api/sandbox/file/write/index.ts create mode 100644 apps/coderouter/src/api/sandbox/pause/index.test.ts create mode 100644 apps/coderouter/src/api/sandbox/pause/index.ts create mode 100644 apps/coderouter/src/api/sandbox/resume/index.test.ts create mode 100644 apps/coderouter/src/api/sandbox/resume/index.ts create mode 100644 apps/coderouter/src/api/sandbox/stop/index.test.ts create mode 100644 apps/coderouter/src/api/sandbox/stop/index.ts create mode 100644 apps/coderouter/src/api/sandbox/url/index.test.ts create mode 100644 apps/coderouter/src/api/sandbox/url/index.ts create mode 100644 apps/coderouter/src/db/connection.ts create mode 100644 apps/coderouter/src/db/schema.ts create mode 100644 apps/coderouter/src/middleware/auth.test.ts create mode 100644 apps/coderouter/src/middleware/auth.ts create mode 100644 apps/coderouter/src/middleware/beforeSandboxCall.ts create mode 100644 apps/coderouter/src/middleware/client.test.ts create mode 100644 apps/coderouter/src/middleware/client.ts create mode 100644 apps/coderouter/src/middleware/error.ts create mode 100644 apps/coderouter/src/middleware/requireSandboxId.test.ts create mode 100644 apps/coderouter/src/middleware/requireSandboxId.ts create mode 100644 apps/coderouter/src/provider/definition/index.test.ts create mode 100644 apps/coderouter/src/provider/definition/index.ts create mode 100644 apps/coderouter/src/provider/definition/sandbox/file/index.ts create mode 100644 apps/coderouter/src/provider/definition/sandbox/index.ts create mode 100644 apps/coderouter/src/provider/e2b/index.ts create mode 100644 apps/coderouter/src/provider/e2b/sandbox/file/index.test.ts create mode 100644 apps/coderouter/src/provider/e2b/sandbox/file/index.ts create mode 100644 apps/coderouter/src/provider/e2b/sandbox/index.ts create mode 100644 apps/coderouter/src/provider/index.ts create mode 100644 apps/coderouter/src/server.ts create mode 100644 apps/coderouter/src/util/auth.ts create mode 100644 apps/coderouter/tsconfig.json create mode 100644 apps/coderouter/typedoc.json create mode 100644 apps/docker-compose.yaml create mode 100644 apps/nginx/bin/genkeys.sh create mode 100644 apps/nginx/conf.d/server.conf create mode 100644 apps/nginx/ssl/README.md create mode 100644 apps/nginx/ssl/onlook-internal.crt create mode 100644 apps/nginx/ssl/onlook-internal.key create mode 100644 apps/nginx/ssl/openssl.cnf create mode 100644 apps/web/template/e2b.Dockerfile create mode 100644 apps/web/template/e2b.toml.example create mode 100644 packages/code-provider/openapitools.json create mode 100644 packages/code-provider/src/providers/coderouter/codegen/.openapi-generator-ignore create mode 100644 packages/code-provider/src/providers/coderouter/codegen/.openapi-generator/FILES create mode 100644 packages/code-provider/src/providers/coderouter/codegen/.openapi-generator/VERSION create mode 100644 packages/code-provider/src/providers/coderouter/codegen/apis/DefaultApi.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/apis/index.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/index.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiAuthSignPost200Response.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiAuthSignPost401Response.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiAuthSignPostRequest.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxCreatePost200Response.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxCreatePostRequest.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileCopyPostRequest.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileDeletePostRequest.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileDownloadPostRequest.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileListPost200Response.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileListPost200ResponseFilesInner.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileListPostRequest.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileReadPost200Response.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileReadPostRequest.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileRenamePostRequest.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileStatPost200Response.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileStatPostRequest.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileWritePostRequest.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileWritePostRequestFilesInner.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxUrlPost200Response.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/index.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/runtime.ts create mode 100644 packages/code-provider/src/providers/coderouter/index.ts diff --git a/README.md b/README.md index b9bc1c2815..be8d8336f1 100644 --- a/README.md +++ b/README.md @@ -69,31 +69,32 @@ builders. ## What you can do with Onlook: - [x] Create Next.js app in seconds - - [x] Start from text or image - - [ ] Use prebuilt templates - - [ ] Import from Figma - - [ ] Start from GitHub repo + - [x] Start from text or image + - [ ] Use prebuilt templates + - [ ] Import from Figma + - [ ] Start from GitHub repo - [x] Visually edit your app - - [x] Use Figma-like UI - - [x] Preview your app in real-time - - [x] Manage brand assets and tokens - - [x] Create and navigate to Pages - - [x] Browse layers - - [x] Manage project Images - - [ ] Detect and use Components – _Previously in - [Onlook Desktop](https://github.com/onlook-dev/desktop)_ + - [x] Use Figma-like UI + - [x] Preview your app in real-time + - [x] Manage brand assets and tokens + - [x] Create and navigate to Pages + - [x] Browse layers + - [x] Manage project Images + - [ ] Detect and use Components – _Previously in + [Onlook Desktop](https://github.com/onlook-dev/desktop)_ - [x] Development Tools - - [x] Real-time code editor - - [x] Save and restore from checkpoints - - [x] Run commands via CLI - - [x] Connect with app marketplace + + - [x] Real-time code editor + - [x] Save and restore from checkpoints + - [x] Run commands via CLI + - [x] Connect with app marketplace - [x] Deploy your app in seconds - - [x] Generate sharable links - - [x] Link your custom domain + - [x] Generate sharable links + - [x] Link your custom domain - [ ] Collaborate with your team - - [ ] Real-time editing - - [ ] Leave comments + - [ ] Real-time editing + - [ ] Leave comments ![Onlook-GitHub-Example](https://github.com/user-attachments/assets/642de37a-72cc-4056-8eb7-8eb42714cdc4) @@ -183,6 +184,7 @@ For a full walkthrough, check out our #### Sandbox and hosting - [CodeSandboxSDK](https://codesandbox.io/docs/sdk) - Dev sandbox +- [E2B](https://e2b.dev/docs) - Dev sandbox - [Freestyle](https://www.freestyle.sh/) - Hosting #### Runtime diff --git a/apps/backend/supabase/config.toml b/apps/backend/supabase/config.toml index b5438ff440..121f4a961b 100644 --- a/apps/backend/supabase/config.toml +++ b/apps/backend/supabase/config.toml @@ -10,6 +10,8 @@ max_rows = 100 [auth] site_url = "https://onlook.com" additional_redirect_urls = [ + "https://onlook.internal", + "https://onlook.internal/auth/callback", "http://localhost:3000", "http://localhost:3000/auth/callback", ] diff --git a/apps/coderouter/.env.example b/apps/coderouter/.env.example new file mode 100644 index 0000000000..67ffde383d --- /dev/null +++ b/apps/coderouter/.env.example @@ -0,0 +1,11 @@ +# Prefix the API paths +URL_PATH_PREFIX="/coderouter" + +# Do not share this secret key - used to generate JWT token used in the client +JWT_SECRET_KEY="Replace this with your own secret" + +# Insert your custom API key here and store in your other backend as well. +# It'll be used for server-to-server authentication. +SUPAROUTA_API_KEY="" + +E2B_API_KEY="Get your API key at https://e2b.dev" \ No newline at end of file diff --git a/apps/coderouter/.gitignore b/apps/coderouter/.gitignore new file mode 100644 index 0000000000..1dcef2d9f2 --- /dev/null +++ b/apps/coderouter/.gitignore @@ -0,0 +1,2 @@ +node_modules +.env \ No newline at end of file diff --git a/apps/coderouter/Dockerfile.dev b/apps/coderouter/Dockerfile.dev new file mode 100644 index 0000000000..5907adfafe --- /dev/null +++ b/apps/coderouter/Dockerfile.dev @@ -0,0 +1,14 @@ +FROM alpine:3.21.3 + +WORKDIR /app + +COPY . . + +# ENV CARGO_HOME=/project/etc/cargo +# ENV RUSTUP_HOME=/project/etc/rustup +ENV BUN_INSTALL=/usr/local/bin/bun + +RUN apk add curl bash build-base +RUN curl -fsSL https://bun.sh/install | bash -s "bun-v1.2.5" + +CMD ["/usr/local/bin/bun/bin/bun", "dev"] diff --git a/apps/coderouter/README.md b/apps/coderouter/README.md new file mode 100644 index 0000000000..0149c72eb6 --- /dev/null +++ b/apps/coderouter/README.md @@ -0,0 +1,46 @@ +# Bun + TypeScript API Starter (Hono + Drizzle) + +A batteries-included starter for building an API with **Bun**, **TypeScript**, **Hono**, and **Drizzle ORM**. +It includes: multi-database support (Postgres/MySQL/SQLite) with migrations, unit + functional tests, +OpenAPI generation, Swagger UI, TypeDoc, and a top-tier GitHub toolchain (Actions, Codecov, Renovate, Biome, Dependabot, GitHub Pages). + +## Quickstart + +```bash +bun install +cp .env.example .env +# choose a DB (sqlite by default) +bun db:generate # generate SQL from schema +bun db:migrate # run migrations +bun dev # start the API on http://localhost:3000 +``` + +OpenAPI: http://localhost:3000/openapi.json +Docs (Swagger UI): http://localhost:3000/docs + +### Scripts +- `bun dev` — run in watch mode +- `bun start` — production start +- `bun test` — run all tests with coverage +- `bun lint` — Biome lint/format check +- `bun fmt` — format files with Biome +- `bun db:generate` — generate migrations via drizzle-kit +- `bun db:migrate` — apply migrations +- `bun openapi` — regenerate OpenAPI JSON +- `bun docs` — build TypeDoc to `site/typedoc` + +### Multi-DB +Set `DRIZZLE_DB` to `postgres` | `mysql` | `sqlite` and provide `DATABASE_URL` accordingly. +SQLite works out-of-the-box (`DATABASE_URL=file:./dev.sqlite`). + +### GitHub Pages (Docs) +The `pages.yml` workflow builds and deploys: +- `/openapi.json` -> `/site/api/openapi.json` +- Swagger UI -> `/site/api/` +- TypeDoc -> `/site/typedoc/` +A small `site/index.html` links to both. + +Enable Pages in **Settings → Pages** (source: GitHub Actions). + +## License +MIT diff --git a/apps/coderouter/biome.json b/apps/coderouter/biome.json new file mode 100644 index 0000000000..8bbe87c895 --- /dev/null +++ b/apps/coderouter/biome.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.1.4/schema.json", + "formatter": { "enabled": true, "formatWithErrors": true }, + "linter": { "enabled": true } +} diff --git a/apps/coderouter/bunfig.toml b/apps/coderouter/bunfig.toml new file mode 100644 index 0000000000..844b046e9e --- /dev/null +++ b/apps/coderouter/bunfig.toml @@ -0,0 +1,2 @@ +[install] +exact = false diff --git a/apps/coderouter/codecov.yml b/apps/coderouter/codecov.yml new file mode 100644 index 0000000000..2e7aa286a9 --- /dev/null +++ b/apps/coderouter/codecov.yml @@ -0,0 +1,6 @@ +coverage: + status: + project: + default: + target: 60% + threshold: 5% diff --git a/apps/coderouter/drizzle.config.ts b/apps/coderouter/drizzle.config.ts new file mode 100644 index 0000000000..c63a44a439 --- /dev/null +++ b/apps/coderouter/drizzle.config.ts @@ -0,0 +1,15 @@ +import "dotenv/config"; + +export default { + out: "./drizzle", + schema: "./src/db/schema.ts", + dialect: + process.env.DRIZZLE_DB === "postgres" + ? "postgresql" + : process.env.DRIZZLE_DB === "mysql" + ? "mysql" + : "sqlite", + dbCredentials: { + connectionString: process.env.DATABASE_URL, + }, +}; diff --git a/apps/coderouter/package.json b/apps/coderouter/package.json new file mode 100644 index 0000000000..c6e37bff3c --- /dev/null +++ b/apps/coderouter/package.json @@ -0,0 +1,38 @@ +{ + "name": "coderouter", + "version": "0.0.1", + "private": false, + "type": "module", + "scripts": { + "dev": "bun run --hot src/server.ts", + "start": "bun run src/server.ts", + "test": "bun test --coverage", + "lint": "biome check .", + "fmt": "biome format --write .", + "db:generate": "drizzle-kit generate", + "db:migrate": "bun run scripts/migrate.ts" + }, + "dependencies": { + "@e2b/code-interpreter": "1.5.1", + "@hono/zod-openapi": "^1.1.0", + "@hono/zod-validator": "^0.2.1", + "@types/jsonwebtoken": "^9.0.10", + "better-sqlite3": "^9.4.3", + "dotenv": "^16.4.5", + "drizzle-kit": "^0.27.0", + "drizzle-orm": "^0.33.0", + "hono": "^4.9.2", + "jsonwebtoken": "^9.0.2", + "mysql2": "^3.9.7", + "pg": "^8.11.5", + "zod": "^4.0.17" + }, + "devDependencies": { + "@biomejs/biome": "^2.1.4", + "@types/node": "^22.5.0", + "bun-types": "^1.2.20", + "supertest": "^7.0.0", + "typedoc": "^0.26.6", + "undici": "^6.19.8" + } +} diff --git a/apps/coderouter/renovate.json b/apps/coderouter/renovate.json new file mode 100644 index 0000000000..d173ea70b1 --- /dev/null +++ b/apps/coderouter/renovate.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:recommended"], + "automerge": false +} diff --git a/apps/coderouter/scripts/migrate.ts b/apps/coderouter/scripts/migrate.ts new file mode 100644 index 0000000000..91ae6bc8e3 --- /dev/null +++ b/apps/coderouter/scripts/migrate.ts @@ -0,0 +1,9 @@ +import "dotenv/config"; +import { execSync } from "node:child_process"; + +try { + execSync("bun x drizzle-kit migrate", { stdio: "inherit" }); +} catch (e) { + console.error("Migration failed", e); + process.exit(1); +} diff --git a/apps/coderouter/site/index.html b/apps/coderouter/site/index.html new file mode 100644 index 0000000000..df79a67f0e --- /dev/null +++ b/apps/coderouter/site/index.html @@ -0,0 +1,14 @@ + + + + + Project Docs + + +

Project Documentation

+ + + diff --git a/apps/coderouter/src/api/auth/sign/index.test.ts b/apps/coderouter/src/api/auth/sign/index.test.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/coderouter/src/api/auth/sign/index.ts b/apps/coderouter/src/api/auth/sign/index.ts new file mode 100644 index 0000000000..9786138862 --- /dev/null +++ b/apps/coderouter/src/api/auth/sign/index.ts @@ -0,0 +1,81 @@ +import { LocalHono } from "@/server"; +import { encodeJwtToken, verifyApiKeyFromHeader } from "@/util/auth"; +import { createRoute, z } from "@hono/zod-openapi"; +import { env } from "bun"; + +export interface AuthSignInput { + sandboxId?: string; + userId?: string; +} + +const BodySchema: z.ZodType = z.object({ + sandboxId: z.string().optional().openapi({ + description: + "The ID of the sandbox. This is your own ID. It can be a UUID or any unique string. Required when using a sandbox.", + example: "00000000-0000-0000-0000-000000000000", + }), + userId: z.string().optional().openapi({ + description: + "The ID of the user. This is your own ID. It can be a UUID or any unique string.", + example: "00000000-0000-0000-0000-000000000000", + }), +}); + +const ResponseSchema = z.object({ + jwt: z.string().openapi({ + description: `The JWT token to send when interacting with the API as header "X-Auth-Jwt.".`, + }), +}); + +const route = createRoute({ + method: "post", + path: env.URL_PATH_PREFIX + "/api/auth/sign", + security: [{ apikey: [] }], + request: { + body: { + content: { + "application/json": { + schema: BodySchema, + }, + }, + }, + }, + responses: { + 200: { + content: { + "application/json": { + schema: ResponseSchema, + }, + }, + description: + "Create a new sandbox. If the sandbox already exists, the system will resume the sandbox.", + }, + 401: { + content: { + "application/json": { + schema: z.object({ error: z.string() }), + }, + }, + description: "The API key is invalid.", + }, + }, +}); + +export function api_auth_sign(app: LocalHono) { + app.openapi(route, async (c) => { + if (!verifyApiKeyFromHeader(c.req.header("Authorization"))) { + return c.json({ error: "Unauthorized" }, 401); + } + const body = await c.req.valid("json"); + const jwt = encodeJwtToken({ + sandboxId: body.sandboxId, + userId: body.userId, + }); + return c.json( + { + jwt, + }, + 200 + ); + }); +} diff --git a/apps/coderouter/src/api/sandbox/create/index.test.ts b/apps/coderouter/src/api/sandbox/create/index.test.ts new file mode 100644 index 0000000000..b3e95269a4 --- /dev/null +++ b/apps/coderouter/src/api/sandbox/create/index.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'bun:test'; +import { Hono } from 'hono'; +import { api_sandbox_create } from './index'; + +describe('sandbox create endpoints', () => { + it('POST /coderouter/api/sandbox/create returns empty object', async () => { + const app = new Hono(); + api_sandbox_create(app); + + const response = await app.request(env.URL_PATH_PREFIX + '/api/sandbox/create', { + method: 'POST', + }); + + expect(response.status).toBe(201); + const body = await response.json(); + expect(body).toEqual({}); + }); +}); diff --git a/apps/coderouter/src/api/sandbox/create/index.ts b/apps/coderouter/src/api/sandbox/create/index.ts new file mode 100644 index 0000000000..e37b348108 --- /dev/null +++ b/apps/coderouter/src/api/sandbox/create/index.ts @@ -0,0 +1,76 @@ +import { ClientError, ClientErrorCode } from "@/provider/definition"; +import { SandboxCreateInput } from "@/provider/definition/sandbox"; +import { LocalHono } from "@/server"; +import { JwtAuthResponses } from "@/util/auth"; +import { createRoute, z } from "@hono/zod-openapi"; +import { env } from "bun"; + +const BodySchema: z.ZodType = z.object({ + templateId: z.string().openapi({ + description: "The ID of the template to use for the sandbox.", + example: "00000000-0000-0000-0000-000000000000", + }), + metadata: z.record(z.string(), z.string()).openapi({ + description: "The metadata of the sandbox.", + }), +}); + +const ResponseSchema = z.object({ + id: z.string().openapi({ + description: + "The ID of the sandbox. This is your own ID. It can be a UUID or any unique string.", + example: "00000000-0000-0000-0000-000000000000", + }), +}); + +const route = createRoute({ + method: "post", + path: env.URL_PATH_PREFIX + "/api/sandbox/create", + security: [{ jwt: [] }], + request: { + body: { + content: { + "application/json": { + schema: BodySchema, + }, + }, + }, + }, + responses: { + 200: { + content: { + "application/json": { + schema: ResponseSchema, + }, + }, + description: + "Create a new sandbox. If the sandbox already exists, the system will resume the sandbox.", + }, + ...JwtAuthResponses, + }, +}); + +export function api_sandbox_create(app: LocalHono) { + app.openapi(route, async (c) => { + const body = await c.req.valid("json"); + try { + await c.get("client").sandbox.get({}); + await c.get("client").sandbox.resume({}); + } catch (e) { + if ( + e instanceof ClientError && + e.code === ClientErrorCode.SandboxNotFound + ) { + await c.get("client").sandbox.create(body); + } else { + throw e; + } + } + return c.json( + { + id: c.get("auth").sandboxId!, + }, + 200 + ); + }); +} diff --git a/apps/coderouter/src/api/sandbox/file/copy/index.test.ts b/apps/coderouter/src/api/sandbox/file/copy/index.test.ts new file mode 100644 index 0000000000..99e984e2f0 --- /dev/null +++ b/apps/coderouter/src/api/sandbox/file/copy/index.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'bun:test'; +import { Hono } from 'hono'; +import { api_sandbox_file_copy } from './index'; + +describe('sandbox files copy endpoints', () => { + it('POST /coderouter/api/sandbox/file/copy returns empty object', async () => { + const app = new Hono(); + api_sandbox_file_copy(app); + + const response = await app.request(env.URL_PATH_PREFIX + '/api/sandbox/file/copy', { + method: 'POST', + }); + + expect(response.status).toBe(200); + const body = await response.json(); + expect(body).toEqual({}); + }); +}); diff --git a/apps/coderouter/src/api/sandbox/file/copy/index.ts b/apps/coderouter/src/api/sandbox/file/copy/index.ts new file mode 100644 index 0000000000..5d7c621ae9 --- /dev/null +++ b/apps/coderouter/src/api/sandbox/file/copy/index.ts @@ -0,0 +1,64 @@ +import { + SandboxFileCopyInput, + SandboxFileCopyOutput, +} from "@/provider/definition/sandbox/file"; +import { JwtAuthResponses } from "@/util/auth"; +import { LocalHono } from "@/server"; +import { createRoute, z } from "@hono/zod-openapi"; +import { env } from "bun"; + +const BodySchema: z.ZodType = z.object({ + source: z.string().openapi({ + description: "The path of the source file.", + example: "/path/to/source.txt", + }), + destination: z.string().openapi({ + description: "The path of the destination file.", + example: "/path/to/destination.txt", + }), + overwrite: z.boolean().openapi({ + description: + "Whether to overwrite the destination file if it already exists.", + example: true, + }), + recursive: z.boolean().openapi({ + description: "Whether to copy the file recursively.", + example: true, + }), +}); + +const ResponseSchema: z.ZodType = z.object({}); + +const route = createRoute({ + method: "post", + path: env.URL_PATH_PREFIX + "/api/sandbox/file/copy", + security: [{ jwt: [] }], + request: { + body: { + content: { + "application/json": { + schema: BodySchema, + }, + }, + }, + }, + responses: { + 200: { + content: { + "application/json": { + schema: ResponseSchema, + }, + }, + description: "Copy a file to the sandbox.", + }, + ...JwtAuthResponses, + }, +}); + +export function api_sandbox_file_copy(app: LocalHono) { + app.openapi(route, async (c) => { + const body = await c.req.valid("json"); + const result = await c.get("client").sandbox.file.copy(body); + return c.json(result, 200); + }); +} diff --git a/apps/coderouter/src/api/sandbox/file/delete/index.test.ts b/apps/coderouter/src/api/sandbox/file/delete/index.test.ts new file mode 100644 index 0000000000..573f7a447b --- /dev/null +++ b/apps/coderouter/src/api/sandbox/file/delete/index.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'bun:test'; +import { Hono } from 'hono'; +import { api_sandbox_file_delete } from './index'; + +describe('sandbox files delete endpoints', () => { + it('DELETE /coderouter/api/sandbox/file/delete returns empty object', async () => { + const app = new Hono(); + api_sandbox_file_delete(app); + + const response = await app.request(env.URL_PATH_PREFIX + '/api/sandbox/file/delete', { + method: 'DELETE', + }); + + expect(response.status).toBe(200); + const body = await response.json(); + expect(body).toEqual({}); + }); +}); diff --git a/apps/coderouter/src/api/sandbox/file/delete/index.ts b/apps/coderouter/src/api/sandbox/file/delete/index.ts new file mode 100644 index 0000000000..17cc10c2ab --- /dev/null +++ b/apps/coderouter/src/api/sandbox/file/delete/index.ts @@ -0,0 +1,51 @@ +import { + SandboxFileDeleteInput, + SandboxFileDeleteOutput, +} from "@/provider/definition/sandbox/file"; +import { LocalHono } from "@/server"; +import { JwtAuthResponses } from "@/util/auth"; +import { createRoute, z } from "@hono/zod-openapi"; +import { env } from "bun"; + +const BodySchema: z.ZodType = z.object({ + path: z.string().openapi({ + description: "The path of the file to delete.", + example: "/path/to/file.txt", + }), +}); + +const ResponseSchema: z.ZodType = z.object({}); + +const route = createRoute({ + method: "post", + path: env.URL_PATH_PREFIX + "/api/sandbox/file/delete", + security: [{ jwt: [] }], + request: { + body: { + content: { + "application/json": { + schema: BodySchema, + }, + }, + }, + }, + responses: { + 200: { + content: { + "application/json": { + schema: ResponseSchema, + }, + }, + description: "Delete a file from the sandbox.", + }, + ...JwtAuthResponses, + }, +}); + +export function api_sandbox_file_delete(app: LocalHono) { + app.openapi(route, async (c) => { + const body = await c.req.valid("json"); + const result = await c.get("client").sandbox.file.delete(body); + return c.json(result, 200); + }); +} diff --git a/apps/coderouter/src/api/sandbox/file/download/index.test.ts b/apps/coderouter/src/api/sandbox/file/download/index.test.ts new file mode 100644 index 0000000000..43aa07f20f --- /dev/null +++ b/apps/coderouter/src/api/sandbox/file/download/index.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'bun:test'; +import { Hono } from 'hono'; +import { api_sandbox_file_download } from './index'; + +describe('sandbox files download endpoints', () => { + it('GET /coderouter/api/sandbox/file/download returns empty object', async () => { + const app = new Hono(); + api_sandbox_file_download(app); + + const response = await app.request(env.URL_PATH_PREFIX + '/api/sandbox/file/download', { + method: 'GET', + }); + + expect(response.status).toBe(200); + const body = await response.json(); + expect(body).toEqual({}); + }); +}); diff --git a/apps/coderouter/src/api/sandbox/file/download/index.ts b/apps/coderouter/src/api/sandbox/file/download/index.ts new file mode 100644 index 0000000000..2366ba9e99 --- /dev/null +++ b/apps/coderouter/src/api/sandbox/file/download/index.ts @@ -0,0 +1,51 @@ +import { + SandboxFileDownloadInput, + SandboxFileDownloadOutput, +} from "@/provider/definition/sandbox/file"; +import { LocalHono } from "@/server"; +import { JwtAuthResponses } from "@/util/auth"; +import { createRoute, z } from "@hono/zod-openapi"; +import { env } from "bun"; + +const BodySchema: z.ZodType = z.object({ + path: z.string().openapi({ + description: "The path of the file to download.", + example: "/path/to/file.txt", + }), +}); + +const ResponseSchema: z.ZodType = z.object({}); + +const route = createRoute({ + method: "post", + path: env.URL_PATH_PREFIX + "/api/sandbox/file/download", + security: [{ jwt: [] }], + request: { + body: { + content: { + "application/json": { + schema: BodySchema, + }, + }, + }, + }, + responses: { + 200: { + content: { + "application/json": { + schema: ResponseSchema, + }, + }, + description: "Download a file from the sandbox.", + }, + ...JwtAuthResponses, + }, +}); + +export function api_sandbox_file_download(app: LocalHono) { + app.openapi(route, async (c) => { + const body = await c.req.valid("json"); + const result = await c.get("client").sandbox.file.download(body); + return c.json(result, 200); + }); +} diff --git a/apps/coderouter/src/api/sandbox/file/list/index.test.ts b/apps/coderouter/src/api/sandbox/file/list/index.test.ts new file mode 100644 index 0000000000..8c8d069db7 --- /dev/null +++ b/apps/coderouter/src/api/sandbox/file/list/index.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'bun:test'; +import { Hono } from 'hono'; +import { api_sandbox_file_list } from './index'; + +describe('sandbox files list endpoints', () => { + it('GET /coderouter/api/sandbox/file/list returns empty object', async () => { + const app = new Hono(); + api_sandbox_file_list(app); + + const response = await app.request(env.URL_PATH_PREFIX + '/api/sandbox/file/list', { + method: 'GET', + }); + + expect(response.status).toBe(200); + const body = await response.json(); + expect(body).toEqual({}); + }); +}); diff --git a/apps/coderouter/src/api/sandbox/file/list/index.ts b/apps/coderouter/src/api/sandbox/file/list/index.ts new file mode 100644 index 0000000000..5b6d8b6d3c --- /dev/null +++ b/apps/coderouter/src/api/sandbox/file/list/index.ts @@ -0,0 +1,59 @@ +import { + SandboxFileListInput, + SandboxFileListOutput, +} from "@/provider/definition/sandbox/file"; +import { LocalHono } from "@/server"; +import { JwtAuthResponses } from "@/util/auth"; +import { createRoute, z } from "@hono/zod-openapi"; +import { env } from "bun"; + +const BodySchema: z.ZodType = z.object({ + path: z.string().openapi({ + description: "The path of the directory to list.", + example: "/path/to/directory", + }), +}); + +const ResponseSchema: z.ZodType = z.object({ + files: z.array( + z.object({ + name: z.string(), + path: z.string(), + type: z.enum(["file", "directory"]), + }) + ), +}); + +const route = createRoute({ + method: "post", + path: env.URL_PATH_PREFIX + "/api/sandbox/file/list", + security: [{ jwt: [] }], + request: { + body: { + content: { + "application/json": { + schema: BodySchema, + }, + }, + }, + }, + responses: { + 200: { + content: { + "application/json": { + schema: ResponseSchema, + }, + }, + description: "List files in the sandbox.", + }, + ...JwtAuthResponses, + }, +}); + +export function api_sandbox_file_list(app: LocalHono) { + app.openapi(route, async (c) => { + const body = await c.req.valid("json"); + const result = await c.get("client").sandbox.file.list(body); + return c.json(result, 200); + }); +} diff --git a/apps/coderouter/src/api/sandbox/file/read/index.test.ts b/apps/coderouter/src/api/sandbox/file/read/index.test.ts new file mode 100644 index 0000000000..ace82e05ba --- /dev/null +++ b/apps/coderouter/src/api/sandbox/file/read/index.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'bun:test'; +import { Hono } from 'hono'; +import { api_sandbox_file_read } from './index'; + +describe('sandbox files read endpoints', () => { + it('GET /coderouter/api/sandbox/file/read returns empty object', async () => { + const app = new Hono(); + api_sandbox_file_read(app); + + const response = await app.request(env.URL_PATH_PREFIX + '/api/sandbox/file/read', { + method: 'GET', + }); + + expect(response.status).toBe(200); + const body = await response.json(); + expect(body).toEqual({}); + }); +}); diff --git a/apps/coderouter/src/api/sandbox/file/read/index.ts b/apps/coderouter/src/api/sandbox/file/read/index.ts new file mode 100644 index 0000000000..11e1af33e0 --- /dev/null +++ b/apps/coderouter/src/api/sandbox/file/read/index.ts @@ -0,0 +1,53 @@ +import { + SandboxFileReadInput, + SandboxFileReadOutput, +} from "@/provider/definition/sandbox/file"; +import { JwtAuthResponses } from "@/util/auth"; +import { LocalHono } from "@/server"; +import { createRoute, z } from "@hono/zod-openapi"; +import { env } from "bun"; + +const BodySchema: z.ZodType = z.object({ + path: z.string().openapi({ + description: "The path of the file to read.", + example: "/path/to/file.txt", + }), +}); + +const ResponseSchema: z.ZodType = z.object({ + data: z.string(), +}); + +const route = createRoute({ + method: "post", + path: env.URL_PATH_PREFIX + "/api/sandbox/file/read", + security: [{ jwt: [] }], + request: { + body: { + content: { + "application/json": { + schema: BodySchema, + }, + }, + }, + }, + responses: { + 200: { + content: { + "application/json": { + schema: ResponseSchema, + }, + }, + description: "Read a file from the sandbox.", + }, + }, + ...JwtAuthResponses, +}); + +export function api_sandbox_file_read(app: LocalHono) { + app.openapi(route, async (c) => { + const body = await c.req.valid("json"); + const result = await c.get("client").sandbox.file.read(body); + return c.json(result, 200); + }); +} diff --git a/apps/coderouter/src/api/sandbox/file/rename/index.test.ts b/apps/coderouter/src/api/sandbox/file/rename/index.test.ts new file mode 100644 index 0000000000..fcdcf30771 --- /dev/null +++ b/apps/coderouter/src/api/sandbox/file/rename/index.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'bun:test'; +import { Hono } from 'hono'; +import { api_sandbox_file_rename } from './index'; + +describe('sandbox files rename endpoints', () => { + it('PUT /coderouter/api/sandbox/file/rename returns empty object', async () => { + const app = new Hono(); + api_sandbox_file_rename(app); + + const response = await app.request(env.URL_PATH_PREFIX + '/api/sandbox/file/rename', { + method: 'PUT', + }); + + expect(response.status).toBe(200); + const body = await response.json(); + expect(body).toEqual({}); + }); +}); diff --git a/apps/coderouter/src/api/sandbox/file/rename/index.ts b/apps/coderouter/src/api/sandbox/file/rename/index.ts new file mode 100644 index 0000000000..682eb3b71a --- /dev/null +++ b/apps/coderouter/src/api/sandbox/file/rename/index.ts @@ -0,0 +1,55 @@ +import { + SandboxFileRenameInput, + SandboxFileRenameOutput, +} from "@/provider/definition/sandbox/file"; +import { JwtAuthResponses } from "@/util/auth"; +import { LocalHono } from "@/server"; +import { createRoute, z } from "@hono/zod-openapi"; +import { env } from "bun"; + +const BodySchema: z.ZodType = z.object({ + oldPath: z.string().openapi({ + description: "The path of the file to rename.", + example: "/path/to/file.txt", + }), + newPath: z.string().openapi({ + description: "The new path of the file.", + example: "/path/to/new/file.txt", + }), +}); + +const ResponseSchema: z.ZodType = z.object({}); + +const route = createRoute({ + method: "post", + path: env.URL_PATH_PREFIX + "/api/sandbox/file/rename", + security: [{ jwt: [] }], + request: { + body: { + content: { + "application/json": { + schema: BodySchema, + }, + }, + }, + }, + responses: { + 200: { + content: { + "application/json": { + schema: ResponseSchema, + }, + }, + description: "Rename a file in the sandbox.", + }, + ...JwtAuthResponses, + }, +}); + +export function api_sandbox_file_rename(app: LocalHono) { + app.openapi(route, async (c) => { + const body = await c.req.valid("json"); + const result = await c.get("client").sandbox.file.rename(body); + return c.json(result, 200); + }); +} diff --git a/apps/coderouter/src/api/sandbox/file/stat/index.test.ts b/apps/coderouter/src/api/sandbox/file/stat/index.test.ts new file mode 100644 index 0000000000..46d9afe98b --- /dev/null +++ b/apps/coderouter/src/api/sandbox/file/stat/index.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'bun:test'; +import { Hono } from 'hono'; +import { api_sandbox_file_stat } from './index'; + +describe('sandbox files stat endpoints', () => { + it('GET /coderouter/api/sandbox/file/stat returns empty object', async () => { + const app = new Hono(); + api_sandbox_file_stat(app); + + const response = await app.request(env.URL_PATH_PREFIX + '/api/sandbox/file/stat', { + method: 'GET', + }); + + expect(response.status).toBe(200); + const body = await response.json(); + expect(body).toEqual({}); + }); +}); diff --git a/apps/coderouter/src/api/sandbox/file/stat/index.ts b/apps/coderouter/src/api/sandbox/file/stat/index.ts new file mode 100644 index 0000000000..e67f26db94 --- /dev/null +++ b/apps/coderouter/src/api/sandbox/file/stat/index.ts @@ -0,0 +1,53 @@ +import { + SandboxFileStatInput, + SandboxFileStatOutput, +} from "@/provider/definition/sandbox/file"; +import { JwtAuthResponses } from "@/util/auth"; +import { LocalHono } from "@/server"; +import { createRoute, z } from "@hono/zod-openapi"; +import { env } from "bun"; + +const BodySchema: z.ZodType = z.object({ + path: z.string().openapi({ + description: "The path of the file to stat.", + example: "/path/to/file.txt", + }), +}); + +const ResponseSchema: z.ZodType = z.object({ + type: z.enum(["file", "directory"]), +}); + +const route = createRoute({ + method: "post", + path: env.URL_PATH_PREFIX + "/api/sandbox/file/stat", + security: [{ jwt: [] }], + request: { + body: { + content: { + "application/json": { + schema: BodySchema, + }, + }, + }, + }, + responses: { + 200: { + content: { + "application/json": { + schema: ResponseSchema, + }, + }, + description: "Get the status of a file in the sandbox.", + }, + ...JwtAuthResponses, + }, +}); + +export function api_sandbox_file_stat(app: LocalHono) { + app.openapi(route, async (c) => { + const body = await c.req.valid("json"); + const result = await c.get("client").sandbox.file.stat(body); + return c.json(result, 200); + }); +} diff --git a/apps/coderouter/src/api/sandbox/file/write/index.test.ts b/apps/coderouter/src/api/sandbox/file/write/index.test.ts new file mode 100644 index 0000000000..6336c72869 --- /dev/null +++ b/apps/coderouter/src/api/sandbox/file/write/index.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'bun:test'; +import { Hono } from 'hono'; +import { api_sandbox_file_write } from './index'; + +describe('sandbox files write endpoints', () => { + it('POST /coderouter/api/sandbox/file/write returns empty object', async () => { + const app = new Hono(); + api_sandbox_file_write(app); + + const response = await app.request(env.URL_PATH_PREFIX + '/api/sandbox/file/write', { + method: 'POST', + }); + + expect(response.status).toBe(200); + const body = await response.json(); + expect(body).toEqual({}); + }); +}); diff --git a/apps/coderouter/src/api/sandbox/file/write/index.ts b/apps/coderouter/src/api/sandbox/file/write/index.ts new file mode 100644 index 0000000000..56f6da87ce --- /dev/null +++ b/apps/coderouter/src/api/sandbox/file/write/index.ts @@ -0,0 +1,63 @@ +import { + SandboxFileWriteInput, + SandboxFileWriteOutput, +} from "@/provider/definition/sandbox/file"; +import { LocalHono } from "@/server"; +import { JwtAuthResponses } from "@/util/auth"; +import { createRoute, z } from "@hono/zod-openapi"; +import { env } from "bun"; + +const BodySchema: z.ZodType = z.object({ + files: z.array( + z.object({ + path: z.string().openapi({ + description: "The path of the file to write.", + example: "/path/to/file.txt", + }), + data: z.string().openapi({ + description: "The content of the file to write.", + example: "Hello, world!", + }), + overwrite: z.boolean().openapi({ + description: "Whether to overwrite the file if it already exists.", + example: true, + }), + }) + ), +}); + +const ResponseSchema: z.ZodType = z.object({}); + +const route = createRoute({ + method: "post", + path: env.URL_PATH_PREFIX + "/api/sandbox/file/write", + security: [{ jwt: [] }], + request: { + body: { + content: { + "application/json": { + schema: BodySchema, + }, + }, + }, + }, + responses: { + 200: { + content: { + "application/json": { + schema: ResponseSchema, + }, + }, + description: "Write a file to the sandbox.", + }, + ...JwtAuthResponses, + }, +}); + +export function api_sandbox_file_write(app: LocalHono) { + app.openapi(route, async (c) => { + const body = await c.req.valid("json"); + const result = await c.get("client").sandbox.file.write(body); + return c.json(result, 200); + }); +} diff --git a/apps/coderouter/src/api/sandbox/pause/index.test.ts b/apps/coderouter/src/api/sandbox/pause/index.test.ts new file mode 100644 index 0000000000..1d2cfa5604 --- /dev/null +++ b/apps/coderouter/src/api/sandbox/pause/index.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'bun:test'; +import { Hono } from 'hono'; +import { api_sandbox_pause } from './index'; + +describe('sandbox pause endpoints', () => { + it('POST /coderouter/api/sandbox/pause returns empty object', async () => { + const app = new Hono(); + api_sandbox_pause(app); + + const response = await app.request(env.URL_PATH_PREFIX + '/api/sandbox/pause', { + method: 'POST', + }); + + expect(response.status).toBe(200); + const body = await response.json(); + expect(body).toEqual({}); + }); +}); diff --git a/apps/coderouter/src/api/sandbox/pause/index.ts b/apps/coderouter/src/api/sandbox/pause/index.ts new file mode 100644 index 0000000000..a53d9f5326 --- /dev/null +++ b/apps/coderouter/src/api/sandbox/pause/index.ts @@ -0,0 +1,54 @@ +import { SandboxPauseInput } from "@/provider/definition/sandbox"; +import { LocalHono } from "@/server"; +import { JwtAuthResponses } from "@/util/auth"; +import { createRoute, z } from "@hono/zod-openapi"; +import { env } from "bun"; + +const BodySchema: z.ZodType = z.object({}); + +const ResponseSchema = z.object({ + id: z.string().openapi({ + description: + "The ID of the sandbox. This is your own ID. It can be a UUID or any unique string.", + example: "00000000-0000-0000-0000-000000000000", + }), +}); + +const route = createRoute({ + method: "post", + path: env.URL_PATH_PREFIX + "/api/sandbox/pause", + security: [{ jwt: [] }], + request: { + body: { + content: { + "application/json": { + schema: BodySchema, + }, + }, + }, + }, + responses: { + 200: { + content: { + "application/json": { + schema: ResponseSchema, + }, + }, + description: "Pause the sandbox.", + }, + ...JwtAuthResponses, + }, +}); + +export function api_sandbox_pause(app: LocalHono) { + app.openapi(route, async (c) => { + const body = await c.req.valid("json"); + await c.get("client").sandbox.pause(body); + return c.json( + { + id: c.get("auth").sandboxId!, + }, + 200 + ); + }); +} diff --git a/apps/coderouter/src/api/sandbox/resume/index.test.ts b/apps/coderouter/src/api/sandbox/resume/index.test.ts new file mode 100644 index 0000000000..a05bde127c --- /dev/null +++ b/apps/coderouter/src/api/sandbox/resume/index.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'bun:test'; +import { Hono } from 'hono'; +import { api_sandbox_resume } from './index'; + +describe('sandbox resume endpoints', () => { + it('POST /coderouter/api/sandbox/resume returns empty object', async () => { + const app = new Hono(); + api_sandbox_resume(app); + + const response = await app.request(env.URL_PATH_PREFIX + '/api/sandbox/resume', { + method: 'POST', + }); + + expect(response.status).toBe(200); + const body = await response.json(); + expect(body).toEqual({}); + }); +}); diff --git a/apps/coderouter/src/api/sandbox/resume/index.ts b/apps/coderouter/src/api/sandbox/resume/index.ts new file mode 100644 index 0000000000..01bdf5d950 --- /dev/null +++ b/apps/coderouter/src/api/sandbox/resume/index.ts @@ -0,0 +1,54 @@ +import { SandboxResumeInput } from "@/provider/definition/sandbox"; +import { LocalHono } from "@/server"; +import { JwtAuthResponses } from "@/util/auth"; +import { createRoute, z } from "@hono/zod-openapi"; +import { env } from "bun"; + +const BodySchema: z.ZodType = z.object({}); + +const ResponseSchema = z.object({ + id: z.string().openapi({ + description: + "The ID of the sandbox. This is your own ID. It can be a UUID or any unique string.", + example: "00000000-0000-0000-0000-000000000000", + }), +}); + +const route = createRoute({ + method: "post", + path: env.URL_PATH_PREFIX + "/api/sandbox/resume", + security: [{ jwt: [] }], + request: { + body: { + content: { + "application/json": { + schema: BodySchema, + }, + }, + }, + }, + responses: { + 200: { + content: { + "application/json": { + schema: ResponseSchema, + }, + }, + description: "Resume the sandbox.", + }, + ...JwtAuthResponses, + }, +}); + +export function api_sandbox_resume(app: LocalHono) { + app.openapi(route, async (c) => { + const body = await c.req.valid("json"); + await c.get("client").sandbox.resume(body); + return c.json( + { + id: c.get("auth").sandboxId!, + }, + 200 + ); + }); +} diff --git a/apps/coderouter/src/api/sandbox/stop/index.test.ts b/apps/coderouter/src/api/sandbox/stop/index.test.ts new file mode 100644 index 0000000000..65c0502012 --- /dev/null +++ b/apps/coderouter/src/api/sandbox/stop/index.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'bun:test'; +import { Hono } from 'hono'; +import { api_sandbox_stop } from './index'; + +describe('sandbox stop endpoints', () => { + it('POST /coderouter/api/sandbox/stop returns empty object', async () => { + const app = new Hono(); + api_sandbox_stop(app); + + const response = await app.request(env.URL_PATH_PREFIX + '/api/sandbox/stop', { + method: 'POST', + }); + + expect(response.status).toBe(200); + const body = await response.json(); + expect(body).toEqual({}); + }); +}); diff --git a/apps/coderouter/src/api/sandbox/stop/index.ts b/apps/coderouter/src/api/sandbox/stop/index.ts new file mode 100644 index 0000000000..61e38307fd --- /dev/null +++ b/apps/coderouter/src/api/sandbox/stop/index.ts @@ -0,0 +1,54 @@ +import { SandboxStopInput } from "@/provider/definition/sandbox"; +import { LocalHono } from "@/server"; +import { JwtAuthResponses } from "@/util/auth"; +import { createRoute, z } from "@hono/zod-openapi"; +import { env } from "bun"; + +const BodySchema: z.ZodType = z.object({}); + +const ResponseSchema = z.object({ + id: z.string().openapi({ + description: + "The ID of the sandbox. This is your own ID. It can be a UUID or any unique string.", + example: "00000000-0000-0000-0000-000000000000", + }), +}); + +const route = createRoute({ + method: "post", + path: env.URL_PATH_PREFIX + "/api/sandbox/stop", + security: [{ jwt: [] }], + request: { + body: { + content: { + "application/json": { + schema: BodySchema, + }, + }, + }, + }, + responses: { + 200: { + content: { + "application/json": { + schema: ResponseSchema, + }, + }, + description: "Stop the sandbox.", + }, + ...JwtAuthResponses, + }, +}); + +export function api_sandbox_stop(app: LocalHono) { + app.openapi(route, async (c) => { + const body = await c.req.valid("json"); + await c.get("client").sandbox.stop(body); + return c.json( + { + id: c.get("auth").sandboxId!, + }, + 200 + ); + }); +} diff --git a/apps/coderouter/src/api/sandbox/url/index.test.ts b/apps/coderouter/src/api/sandbox/url/index.test.ts new file mode 100644 index 0000000000..260d15e915 --- /dev/null +++ b/apps/coderouter/src/api/sandbox/url/index.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'bun:test'; +import { Hono } from 'hono'; +import { api_sandbox_url } from './index'; + +describe('sandbox url endpoints', () => { + it('GET /coderouter/api/sandbox/url returns empty object', async () => { + const app = new Hono(); + api_sandbox_url(app); + + const response = await app.request(env.URL_PATH_PREFIX + '/api/sandbox/url', { + method: 'GET', + }); + + expect(response.status).toBe(200); + const body = await response.json(); + expect(body).toEqual({}); + }); +}); diff --git a/apps/coderouter/src/api/sandbox/url/index.ts b/apps/coderouter/src/api/sandbox/url/index.ts new file mode 100644 index 0000000000..467159bd94 --- /dev/null +++ b/apps/coderouter/src/api/sandbox/url/index.ts @@ -0,0 +1,54 @@ +import { SandboxUrlInput } from "@/provider/definition/sandbox"; +import { LocalHono } from "@/server"; +import { JwtAuthResponses } from "@/util/auth"; +import { createRoute, z } from "@hono/zod-openapi"; +import { env } from "bun"; + +const BodySchema: z.ZodType = z.object({}); + +const ResponseSchema = z.object({ + url: z.string().openapi({ + description: "The URL of the sandbox.", + example: "", + }), +}); + +const route = createRoute({ + method: "post", + path: env.URL_PATH_PREFIX + "/api/sandbox/url", + security: [{ jwt: [] }], + // no parameters + // request: { + // body: { + // content: { + // "application/json": { + // schema: BodySchema, + // }, + // }, + // }, + // }, + responses: { + 200: { + content: { + "application/json": { + schema: ResponseSchema, + }, + }, + description: "Get a URL to access the sandbox.", + }, + ...JwtAuthResponses, + }, +}); + +export function api_sandbox_url(app: LocalHono) { + app.openapi(route, async (c) => { + // const body = await c.req.valid("json"); + const res = await c.get("client").sandbox.url({}); + return c.json( + { + url: res.url, + }, + 200 + ); + }); +} diff --git a/apps/coderouter/src/db/connection.ts b/apps/coderouter/src/db/connection.ts new file mode 100644 index 0000000000..cec432874a --- /dev/null +++ b/apps/coderouter/src/db/connection.ts @@ -0,0 +1,37 @@ +import "dotenv/config"; +// import { drizzle } from "drizzle-orm"; +// import { Database } from "drizzle-orm/sqlite-proxy"; +import { mysqlUsers, pgUsers, sqliteUsers } from "./schema"; +// import { createPool as createPgPool } from "pg"; +// import mysql from "mysql2/promise"; +// import DatabaseSqlite from "better-sqlite3"; + +export type DBKind = "postgres" | "mysql" | "sqlite"; + +export const dbKind: DBKind = (process.env.DRIZZLE_DB as DBKind) || "sqlite"; + +export async function getDb() { + // if (dbKind === "postgres") { + // const { Pool } = await import('pg'); // Lazy if needed + // const pool = new Pool({ connectionString: process.env.DATABASE_URL }); + // return drizzle(pool); + // } else if (dbKind === "mysql") { + // return mysql + // .createConnection(process.env.DATABASE_URL!) + // .then((conn) => drizzle(conn)); + // } else { + // const sqlite = new DatabaseSqlite( + // process.env.DATABASE_URL?.replace("file:", "") || "dev.sqlite", + // ); + // // @ts-ignore - drizzle typing + // return drizzle(sqlite); + // } +} + +// helpers to pick correct table (for demo) +export const usersTable = + dbKind === "postgres" + ? pgUsers + : dbKind === "mysql" + ? mysqlUsers + : sqliteUsers; diff --git a/apps/coderouter/src/db/schema.ts b/apps/coderouter/src/db/schema.ts new file mode 100644 index 0000000000..5e49302a12 --- /dev/null +++ b/apps/coderouter/src/db/schema.ts @@ -0,0 +1,32 @@ +import { + int, + mysqlTable, + varchar as mysqlVarchar, +} from "drizzle-orm/mysql-core"; +import { pgTable, serial, varchar } from "drizzle-orm/pg-core"; +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; + +// Simple cross-dialect "users" table definition helpers: +export const sqliteUsers = sqliteTable("users", { + id: integer("id").primaryKey({ autoIncrement: true }), + email: text("email").notNull().unique(), + name: text("name"), +}); + +export const pgUsers = pgTable("users", { + id: serial("id").primaryKey(), + email: varchar("email", { length: 255 }).notNull().unique(), + name: varchar("name", { length: 255 }), +}); + +export const mysqlUsers = mysqlTable("users", { + id: int("id").primaryKey().autoincrement(), + email: mysqlVarchar("email", { length: 255 }).notNull().unique(), + name: mysqlVarchar("name", { length: 255 }), +}); + +export type User = { + id: number; + email: string; + name: string | null; +}; diff --git a/apps/coderouter/src/middleware/auth.test.ts b/apps/coderouter/src/middleware/auth.test.ts new file mode 100644 index 0000000000..ef2f74bd24 --- /dev/null +++ b/apps/coderouter/src/middleware/auth.test.ts @@ -0,0 +1,244 @@ +import { describe, it, expect, beforeEach, afterEach, mock } from 'bun:test'; +import { OpenAPIHono } from '@hono/zod-openapi'; +import { setupAuthJwtMiddleware } from './auth'; +import { decodeJwtToken, encodeJwtToken, AuthJwtPayload } from '@/util/auth'; +import type { LocalHono } from '@/server'; +import { env } from 'bun'; + +// Mock the environment variable +const originalEnv = process.env.JWT_SECRET_KEY; +const mockJwtSecret = 'test-secret-key'; + +describe('Auth JWT Middleware', () => { + let app: LocalHono; + + beforeEach(() => { + // Set up test environment + process.env.JWT_SECRET_KEY = mockJwtSecret; + + // Create a fresh app instance for each test + app = new OpenAPIHono<{ + Variables: { client: any; auth: AuthJwtPayload }; + }>(); + + // Set up the auth middleware + setupAuthJwtMiddleware(app, env.URL_PATH_PREFIX + '/*'); + + // Add a test route to verify middleware works + app.get(env.URL_PATH_PREFIX + '/api/test', (c) => { + const auth = c.get('auth'); + return c.json({ success: true, auth }); + }); + }); + + afterEach(() => { + // Restore original environment + if (originalEnv) { + process.env.JWT_SECRET_KEY = originalEnv; + } else { + delete process.env.JWT_SECRET_KEY; + } + }); + + describe('Missing JWT token', () => { + it('should return 401 when X-Auth-Jwt header is missing', async () => { + const res = await app.request(env.URL_PATH_PREFIX + '/api/test'); + + expect(res.status).toBe(401); + const body = (await res.json()) as { error: string }; + expect(body).toEqual({ error: 'Unauthorized' }); + }); + + it('should return 401 when X-Auth-Jwt header is empty', async () => { + const res = await app.request(env.URL_PATH_PREFIX + '/api/test', { + headers: { + 'X-Auth-Jwt': '', + }, + }); + + expect(res.status).toBe(401); + const body = (await res.json()) as { error: string }; + expect(body).toEqual({ error: 'Unauthorized' }); + }); + }); + + describe('Valid JWT token', () => { + it('should allow request with valid JWT token', async () => { + const payload: AuthJwtPayload = { + sandboxId: 'test-sandbox-123', + userId: 'test-user-456', + }; + + const token = encodeJwtToken(payload); + + const res = await app.request(env.URL_PATH_PREFIX + '/api/test', { + headers: { + 'X-Auth-Jwt': token, + }, + }); + + expect(res.status).toBe(200); + const body = (await res.json()) as { + success: boolean; + auth: AuthJwtPayload; + }; + expect(body.success).toBe(true); + expect(body.auth.sandboxId).toBe(payload.sandboxId!); + expect(body.auth.userId).toBe(payload.userId!); + }); + + it('should allow request with minimal JWT payload', async () => { + const payload: AuthJwtPayload = {}; + + const token = encodeJwtToken(payload); + + const res = await app.request(env.URL_PATH_PREFIX + '/api/test', { + headers: { + 'X-Auth-Jwt': token, + }, + }); + + expect(res.status).toBe(200); + const body = (await res.json()) as { + success: boolean; + auth: AuthJwtPayload; + }; + expect(body.success).toBe(true); + expect(body.auth.sandboxId).toBeUndefined(); + expect(body.auth.userId).toBeUndefined(); + }); + + it('should set auth context for downstream handlers', async () => { + const payload: AuthJwtPayload = { + sandboxId: 'sandbox-789', + userId: 'user-101', + }; + + const token = encodeJwtToken(payload); + + // Add a custom handler that checks the auth context + app.get(env.URL_PATH_PREFIX + '/api/auth-test', (c) => { + const auth = c.get('auth'); + return c.json({ + sandboxId: auth.sandboxId, + userId: auth.userId, + }); + }); + + const res = await app.request(env.URL_PATH_PREFIX + '/api/auth-test', { + headers: { + 'X-Auth-Jwt': token, + }, + }); + + expect(res.status).toBe(200); + const body = (await res.json()) as { + sandboxId?: string; + userId?: string; + }; + expect(body.sandboxId).toBe('sandbox-789'); + expect(body.userId).toBe('user-101'); + }); + }); + + describe('Invalid JWT token', () => { + it('should return 401 when JWT token is malformed', async () => { + const res = await app.request(env.URL_PATH_PREFIX + '/api/test', { + headers: { + 'X-Auth-Jwt': 'invalid-token', + }, + }); + + expect(res.status).toBe(401); + const body = (await res.json()) as { error: string }; + expect(body).toEqual({ + error: 'Failed to decode JWT. Fetch a new JWT token.', + }); + }); + + it('should return 401 when JWT token is signed with wrong secret', async () => { + // Create a token with a different secret + const payload: AuthJwtPayload = { sandboxId: 'test' }; + const wrongToken = encodeJwtToken(payload); + + // Change the secret to make the token invalid + process.env.JWT_SECRET_KEY = 'different-secret'; + + const res = await app.request(env.URL_PATH_PREFIX + '/api/test', { + headers: { + 'X-Auth-Jwt': wrongToken, + }, + }); + + expect(res.status).toBe(401); + const body = (await res.json()) as { error: string }; + expect(body).toEqual({ + error: 'Failed to decode JWT. Fetch a new JWT token.', + }); + }); + + it('should return 401 when JWT token is expired', async () => { + // Create an expired token by using a past expiration time + const payload: AuthJwtPayload = { sandboxId: 'test' }; + const expiredToken = encodeJwtToken(payload); + + // Temporarily change the JWT secret to make the token invalid + const originalSecret = process.env.JWT_SECRET_KEY; + process.env.JWT_SECRET_KEY = 'different-secret-for-expired-test'; + + const res = await app.request(env.URL_PATH_PREFIX + '/api/test', { + headers: { + 'X-Auth-Jwt': expiredToken, + }, + }); + + expect(res.status).toBe(401); + const body = (await res.json()) as { error: string }; + expect(body).toEqual({ + error: 'Failed to decode JWT. Fetch a new JWT token.', + }); + + // Restore original secret + process.env.JWT_SECRET_KEY = originalSecret; + }); + }); + + describe('Middleware scope', () => { + it('should only apply to /coderouter/api/* routes', async () => { + // Add a route outside the /api scope + app.get('/public/test', (c) => { + return c.json({ public: true }); + }); + + const res = await app.request('/public/test'); + + expect(res.status).toBe(200); + const body = (await res.json()) as { public: boolean }; + expect(body).toEqual({ public: true }); + }); + + it('should apply to nested /api routes', async () => { + app.get(env.URL_PATH_PREFIX + '/api/nested/deep/route', (c) => { + const auth = c.get('auth'); + return c.json({ nested: true, auth }); + }); + + const payload: AuthJwtPayload = { sandboxId: 'nested-test' }; + const token = encodeJwtToken(payload); + + const res = await app.request(env.URL_PATH_PREFIX + '/api/nested/deep/route', { + headers: { + 'X-Auth-Jwt': token, + }, + }); + + expect(res.status).toBe(200); + const body = (await res.json()) as { + nested: boolean; + auth: AuthJwtPayload; + }; + expect(body.nested).toBe(true); + expect(body.auth.sandboxId).toBe(payload.sandboxId!); + }); + }); +}); diff --git a/apps/coderouter/src/middleware/auth.ts b/apps/coderouter/src/middleware/auth.ts new file mode 100644 index 0000000000..bd64d6d474 --- /dev/null +++ b/apps/coderouter/src/middleware/auth.ts @@ -0,0 +1,23 @@ +import { LocalHono } from "@/server"; +import { AuthJwtPayload, decodeJwtToken } from "@/util/auth"; + +export function setupAuthJwtMiddleware(app: LocalHono, path: string) { + return app.use(path, async (c, next) => { + const jwt = c.req.header("X-Auth-Jwt"); + if (!jwt) { + return c.json({ error: "Unauthorized" }, 401); + } + try { + const payload = decodeJwtToken(jwt) as AuthJwtPayload; + c.set("auth", payload); + } catch (e) { + console.error("Failed to decode JWT.", e); + return c.json( + { error: "Failed to decode JWT. Fetch a new JWT token." }, + 401 + ); + } + + await next(); + }); +} diff --git a/apps/coderouter/src/middleware/beforeSandboxCall.ts b/apps/coderouter/src/middleware/beforeSandboxCall.ts new file mode 100644 index 0000000000..c824012b6b --- /dev/null +++ b/apps/coderouter/src/middleware/beforeSandboxCall.ts @@ -0,0 +1,15 @@ +import { LocalHono } from "@/server"; + +export function setupBeforeSandboxCallMiddleware(app: LocalHono, path: string) { + return app.use(path, async (c, next) => { + const client = c.get("client"); + if (!client) { + console.error( + "The provider client is not set. Please check the middleware setup." + ); + return c.json({ error: "Client not found" }, 500); + } + await client.sandbox.beforeSandboxCall(); + await next(); + }); +} diff --git a/apps/coderouter/src/middleware/client.test.ts b/apps/coderouter/src/middleware/client.test.ts new file mode 100644 index 0000000000..4b1b48e72c --- /dev/null +++ b/apps/coderouter/src/middleware/client.test.ts @@ -0,0 +1,277 @@ +import { describe, it, expect, beforeEach, afterEach } from 'bun:test'; +import { OpenAPIHono } from '@hono/zod-openapi'; +import { setupClientMiddleware } from './client'; +import { setupAuthJwtMiddleware } from './auth'; +import type { LocalHono } from '@/server'; +import { AuthJwtPayload } from '@/util/auth'; +import { encodeJwtToken } from '@/util/auth'; + +// Store original environment +const originalEnv = process.env.E2B_API_KEY; +const originalJwtEnv = process.env.JWT_SECRET_KEY; + +describe('setupClientMiddleware', () => { + let app: LocalHono; + + beforeEach(() => { + // Set up test environment + process.env.JWT_SECRET_KEY = 'test-jwt-secret'; + + // Create a fresh app instance for each test + app = new OpenAPIHono<{ + Variables: { client: any; auth: AuthJwtPayload }; + }>(); + + // Set up auth middleware first (required for client middleware) + setupAuthJwtMiddleware(app); + + // Set up client middleware + setupClientMiddleware(app); + + // Add a test route to verify middleware works + app.get(env.URL_PATH_PREFIX + '/api/test', (c) => { + const client = c.get('client'); + const auth = c.get('auth'); + return c.json({ + success: true, + hasClient: !!client, + clientType: client?.constructor?.name, + auth, + }); + }); + }); + + afterEach(() => { + // Restore original environment + if (originalEnv) { + process.env.E2B_API_KEY = originalEnv; + } else { + delete process.env.E2B_API_KEY; + } + + if (originalJwtEnv) { + process.env.JWT_SECRET_KEY = originalJwtEnv; + } else { + delete process.env.JWT_SECRET_KEY; + } + }); + + describe('when E2B_API_KEY is set', () => { + beforeEach(() => { + process.env.E2B_API_KEY = 'test-api-key'; + }); + + it('should create E2BClient and set it in context', async () => { + const payload: AuthJwtPayload = { + sandboxId: 'test-sandbox-id', + userId: 'test-user-id', + }; + + const token = encodeJwtToken(payload); + + const res = await app.request(env.URL_PATH_PREFIX + '/api/test', { + headers: { + 'X-Auth-Jwt': token, + }, + }); + + expect(res.status).toBe(200); + const body = (await res.json()) as { + success: boolean; + hasClient: boolean; + clientType: string; + auth: AuthJwtPayload; + }; + expect(body.success).toBe(true); + expect(body.hasClient).toBe(true); + expect(body.clientType).toBe('E2BClient'); + expect(body.auth.sandboxId).toBe('test-sandbox-id'); + expect(body.auth.userId).toBe('test-user-id'); + }); + + it('should handle auth data with undefined values', async () => { + const payload: AuthJwtPayload = { + sandboxId: undefined, + userId: undefined, + }; + + const token = encodeJwtToken(payload); + + const res = await app.request(env.URL_PATH_PREFIX + '/api/test', { + headers: { + 'X-Auth-Jwt': token, + }, + }); + + expect(res.status).toBe(200); + const body = (await res.json()) as { + success: boolean; + hasClient: boolean; + clientType: string; + auth: AuthJwtPayload; + }; + expect(body.success).toBe(true); + expect(body.hasClient).toBe(true); + expect(body.auth.sandboxId).toBeUndefined(); + expect(body.auth.userId).toBeUndefined(); + }); + + it('should handle partial auth data', async () => { + const payload: AuthJwtPayload = { + sandboxId: 'test-sandbox-id', + userId: undefined, + }; + + const token = encodeJwtToken(payload); + + const res = await app.request(env.URL_PATH_PREFIX + '/api/test', { + headers: { + 'X-Auth-Jwt': token, + }, + }); + + expect(res.status).toBe(200); + const body = (await res.json()) as { + success: boolean; + hasClient: boolean; + clientType: string; + auth: AuthJwtPayload; + }; + expect(body.success).toBe(true); + expect(body.hasClient).toBe(true); + expect(body.auth.sandboxId).toBe('test-sandbox-id'); + expect(body.auth.userId).toBeUndefined(); + }); + }); + + describe('when E2B_API_KEY is not set', () => { + beforeEach(() => { + process.env.E2B_API_KEY = undefined; + }); + + it('should return 500 error response and not continue to route handler', async () => { + const payload: AuthJwtPayload = { + sandboxId: 'test-sandbox-id', + userId: 'test-user-id', + }; + + const token = encodeJwtToken(payload); + + const res = await app.request(env.URL_PATH_PREFIX + '/api/test', { + headers: { + 'X-Auth-Jwt': token, + }, + }); + + expect(res.status).toBe(500); + const body = (await res.json()) as { error: string }; + expect(body).toEqual({ + error: '"E2B_API_KEY" is not set', + }); + }); + + it('should return 500 error response even with valid auth data', async () => { + const payload: AuthJwtPayload = { + sandboxId: 'test-sandbox-id', + userId: 'test-user-id', + }; + + const token = encodeJwtToken(payload); + + const res = await app.request(env.URL_PATH_PREFIX + '/api/test', { + headers: { + 'X-Auth-Jwt': token, + }, + }); + + expect(res.status).toBe(500); + const body = (await res.json()) as { error: string }; + expect(body).toEqual({ + error: '"E2B_API_KEY" is not set', + }); + }); + + it('should return 500 error response for any API route', async () => { + // Add another API route + app.get(env.URL_PATH_PREFIX + '/api/another-test', (c) => { + return c.json({ another: true }); + }); + + const payload: AuthJwtPayload = { + sandboxId: 'test-sandbox-id', + userId: 'test-user-id', + }; + + const token = encodeJwtToken(payload); + + const res = await app.request(env.URL_PATH_PREFIX + '/api/another-test', { + headers: { + 'X-Auth-Jwt': token, + }, + }); + + expect(res.status).toBe(500); + const body = (await res.json()) as { error: string }; + expect(body).toEqual({ + error: '"E2B_API_KEY" is not set', + }); + }); + }); + + describe('middleware integration', () => { + it('should apply middleware to /coderouter/api/* routes', () => { + process.env.E2B_API_KEY = 'test-api-key'; + const result = setupClientMiddleware(app); + + // The middleware should return the app + expect(result).toBe(app); + }); + + it('should not apply middleware to non-api routes', async () => { + process.env.E2B_API_KEY = 'test-api-key'; + + // Add a route outside the /api scope + app.get('/public/test', (c) => { + return c.json({ public: true }); + }); + + const res = await app.request('/public/test'); + + expect(res.status).toBe(200); + const body = (await res.json()) as { public: boolean }; + expect(body).toEqual({ public: true }); + }); + + it('should handle multiple requests correctly', async () => { + process.env.E2B_API_KEY = 'test-api-key'; + + const payload: AuthJwtPayload = { + sandboxId: 'test-sandbox-id', + userId: 'test-user-id', + }; + + const token = encodeJwtToken(payload); + + // Make multiple requests + const res1 = await app.request(env.URL_PATH_PREFIX + '/api/test', { + headers: { + 'X-Auth-Jwt': token, + }, + }); + + const res2 = await app.request(env.URL_PATH_PREFIX + '/api/test', { + headers: { + 'X-Auth-Jwt': token, + }, + }); + + expect(res1.status).toBe(200); + expect(res2.status).toBe(200); + + const body1 = (await res1.json()) as { hasClient: boolean }; + const body2 = (await res2.json()) as { hasClient: boolean }; + expect(body1.hasClient).toBe(true); + expect(body2.hasClient).toBe(true); + }); + }); +}); diff --git a/apps/coderouter/src/middleware/client.ts b/apps/coderouter/src/middleware/client.ts new file mode 100644 index 0000000000..cd4f63bd26 --- /dev/null +++ b/apps/coderouter/src/middleware/client.ts @@ -0,0 +1,29 @@ +import { E2BClient } from "@/provider/e2b"; +import { env } from "bun"; +import { LocalHono } from "@/server"; + +export function setupClientMiddleware(app: LocalHono, path: string) { + return app.use(path, async (c, next) => { + if (env.E2B_API_KEY) { + const auth = c.get("auth"); + c.set( + "client", + new E2BClient( + { + sandboxId: auth?.sandboxId, + userId: auth?.userId, + }, + env.E2B_API_KEY + ) + ); + await next(); + } else { + return c.json( + { + error: `"E2B_API_KEY" is not set`, + }, + 500 + ); + } + }); +} diff --git a/apps/coderouter/src/middleware/error.ts b/apps/coderouter/src/middleware/error.ts new file mode 100644 index 0000000000..9d8af56952 --- /dev/null +++ b/apps/coderouter/src/middleware/error.ts @@ -0,0 +1,27 @@ +import { LocalHono } from "@/server"; +import { ClientError } from "@/provider/definition"; +import { HTTPException } from "hono/http-exception"; +import { ContentfulStatusCode } from "hono/utils/http-status"; + +export function setupErrorMiddleware(app: LocalHono) { + return app.onError(async (err, c) => { + if (err instanceof ClientError) { + const res = c.json( + { + error: err.message, + }, + err.status as ContentfulStatusCode + ); + return res; + } + console.error( + `[${c.req.method}] ${c.req.url}`, + "Unhandled error. Please convert to ClientError.", + err.toString() + ); + throw new HTTPException(500, { + message: "Internal server error", + cause: err, + }); + }); +} diff --git a/apps/coderouter/src/middleware/requireSandboxId.test.ts b/apps/coderouter/src/middleware/requireSandboxId.test.ts new file mode 100644 index 0000000000..6a21c9e393 --- /dev/null +++ b/apps/coderouter/src/middleware/requireSandboxId.test.ts @@ -0,0 +1,318 @@ +import { describe, it, expect } from "bun:test"; +import { OpenAPIHono } from "@hono/zod-openapi"; +import { setupRequiredSandboxIdMiddleware } from "./requireSandboxId"; +import { AuthJwtPayload } from "@/util/auth"; +import type { LocalHono } from "@/server"; + +describe("requireSandboxId middleware", () => { + describe("when sandboxId is present in auth context", () => { + it("should allow the request to proceed when sandboxId is a string", async () => { + const app = new OpenAPIHono<{ + Variables: { client: any; auth: AuthJwtPayload }; + }>(); + + // Set up auth context first + app.use(env.URL_PATH_PREFIX + "/api/test", async (c, next) => { + c.set("auth", { + sandboxId: "test-sandbox-123", + userId: "test-user-456", + }); + await next(); + }); + + // Apply the requireSandboxId middleware + setupRequiredSandboxIdMiddleware(app, env.URL_PATH_PREFIX + "/api/test"); + + // Add the route handler + app.get(env.URL_PATH_PREFIX + "/api/test", (c) => { + const auth = c.get("auth"); + return c.json({ + success: true, + sandboxId: auth?.sandboxId, + userId: auth?.userId, + }); + }); + + const res = await app.request(env.URL_PATH_PREFIX + "/api/test"); + + expect(res.status).toBe(200); + const body = (await res.json()) as { + success: boolean; + sandboxId: string; + userId: string; + }; + expect(body.success).toBe(true); + expect(body.sandboxId).toBe("test-sandbox-123"); + expect(body.userId).toBe("test-user-456"); + }); + + it("should allow the request to proceed when sandboxId is present but userId is undefined", async () => { + const app = new OpenAPIHono<{ + Variables: { client: any; auth: AuthJwtPayload }; + }>(); + + // Set up auth context first + app.use(env.URL_PATH_PREFIX + "/api/test", async (c, next) => { + c.set("auth", { + sandboxId: "test-sandbox-123", + userId: undefined, + }); + await next(); + }); + + // Apply the requireSandboxId middleware + setupRequiredSandboxIdMiddleware(app, env.URL_PATH_PREFIX + "/api/test"); + + // Add the route handler + app.get(env.URL_PATH_PREFIX + "/api/test", (c) => { + const auth = c.get("auth"); + return c.json({ + success: true, + sandboxId: auth?.sandboxId, + userId: auth?.userId, + }); + }); + + const res = await app.request(env.URL_PATH_PREFIX + "/api/test"); + + expect(res.status).toBe(200); + const body = (await res.json()) as { + success: boolean; + sandboxId: string; + userId: string | undefined; + }; + expect(body.success).toBe(true); + expect(body.sandboxId).toBe("test-sandbox-123"); + expect(body.userId).toBeUndefined(); + }); + + it("should allow the request to proceed when only sandboxId is present", async () => { + const app = new OpenAPIHono<{ + Variables: { client: any; auth: AuthJwtPayload }; + }>(); + + // Set up auth context first + app.use(env.URL_PATH_PREFIX + "/api/test", async (c, next) => { + c.set("auth", { + sandboxId: "test-sandbox-123", + }); + await next(); + }); + + // Apply the requireSandboxId middleware + setupRequiredSandboxIdMiddleware(app, env.URL_PATH_PREFIX + "/api/test"); + + // Add the route handler + app.get(env.URL_PATH_PREFIX + "/api/test", (c) => { + const auth = c.get("auth"); + return c.json({ + success: true, + sandboxId: auth?.sandboxId, + userId: auth?.userId, + }); + }); + + const res = await app.request(env.URL_PATH_PREFIX + "/api/test"); + + expect(res.status).toBe(200); + const body = (await res.json()) as { + success: boolean; + sandboxId: string; + userId: string | undefined; + }; + expect(body.success).toBe(true); + expect(body.sandboxId).toBe("test-sandbox-123"); + expect(body.userId).toBeUndefined(); + }); + }); + + describe("when sandboxId is missing from auth context", () => { + it("should return 400 error when sandboxId is undefined", async () => { + const app = new OpenAPIHono<{ + Variables: { client: any; auth: AuthJwtPayload }; + }>(); + + // Set up auth context first + app.use(env.URL_PATH_PREFIX + "/api/test", async (c, next) => { + c.set("auth", { + sandboxId: undefined, + userId: "test-user-456", + }); + await next(); + }); + + // Apply the requireSandboxId middleware + setupRequiredSandboxIdMiddleware(app, env.URL_PATH_PREFIX + "/api/test"); + + // Add the route handler (should not be reached) + app.get(env.URL_PATH_PREFIX + "/api/test", (c) => { + const auth = c.get("auth"); + return c.json({ + success: true, + sandboxId: auth?.sandboxId, + userId: auth?.userId, + }); + }); + + const res = await app.request(env.URL_PATH_PREFIX + "/api/test"); + + expect(res.status).toBe(400); + const body = (await res.json()) as { error: string }; + expect(body.error).toBe('"sandboxId" is not set in JWT token.'); + }); + + it("should return 400 error when sandboxId is not present in auth object", async () => { + const app = new OpenAPIHono<{ + Variables: { client: any; auth: AuthJwtPayload }; + }>(); + + // Set up auth context first + app.use(env.URL_PATH_PREFIX + "/api/test", async (c, next) => { + c.set("auth", { + userId: "test-user-456", + }); + await next(); + }); + + // Apply the requireSandboxId middleware + setupRequiredSandboxIdMiddleware(app, env.URL_PATH_PREFIX + "/api/test"); + + // Add the route handler (should not be reached) + app.get(env.URL_PATH_PREFIX + "/api/test", (c) => { + const auth = c.get("auth"); + return c.json({ + success: true, + sandboxId: auth?.sandboxId, + userId: auth?.userId, + }); + }); + + const res = await app.request(env.URL_PATH_PREFIX + "/api/test"); + + expect(res.status).toBe(400); + const body = (await res.json()) as { error: string }; + expect(body.error).toBe('"sandboxId" is not set in JWT token.'); + }); + + it("should return 400 error when auth object is empty", async () => { + const app = new OpenAPIHono<{ + Variables: { client: any; auth: AuthJwtPayload }; + }>(); + + // Set up auth context first + app.use(env.URL_PATH_PREFIX + "/api/test", async (c, next) => { + c.set("auth", {}); + await next(); + }); + + // Apply the requireSandboxId middleware + setupRequiredSandboxIdMiddleware(app, env.URL_PATH_PREFIX + "/api/test"); + + // Add the route handler (should not be reached) + app.get(env.URL_PATH_PREFIX + "/api/test", (c) => { + const auth = c.get("auth"); + return c.json({ + success: true, + sandboxId: auth?.sandboxId, + userId: auth?.userId, + }); + }); + + const res = await app.request(env.URL_PATH_PREFIX + "/api/test"); + + expect(res.status).toBe(400); + const body = (await res.json()) as { error: string }; + expect(body.error).toBe('"sandboxId" is not set in JWT token.'); + }); + + it("should return 400 error when auth context is not set", async () => { + const app = new OpenAPIHono<{ + Variables: { client: any; auth: AuthJwtPayload }; + }>(); + + // Apply the requireSandboxId middleware without setting auth context + setupRequiredSandboxIdMiddleware(app, env.URL_PATH_PREFIX + "/api/test"); + + // Add the route handler (should not be reached) + app.get(env.URL_PATH_PREFIX + "/api/test", (c) => { + const auth = c.get("auth"); + return c.json({ + success: true, + sandboxId: auth?.sandboxId, + userId: auth?.userId, + }); + }); + + const res = await app.request(env.URL_PATH_PREFIX + "/api/test"); + + expect(res.status).toBe(400); + const body = (await res.json()) as { error: string }; + expect(body.error).toBe('"sandboxId" is not set in JWT token.'); + }); + }); + + describe("middleware path matching", () => { + it("should only apply to the specified path", async () => { + const app = new OpenAPIHono<{ + Variables: { client: any; auth: AuthJwtPayload }; + }>(); + + // Apply the requireSandboxId middleware to a specific path + setupRequiredSandboxIdMiddleware(app, env.URL_PATH_PREFIX + "/api/test"); + + // Add a route that doesn't use the middleware + app.get(env.URL_PATH_PREFIX + "/api/other", (c) => { + return c.json({ success: true, message: "other route" }); + }); + + const res = await app.request(env.URL_PATH_PREFIX + "/api/other"); + + expect(res.status).toBe(200); + const body = (await res.json()) as { success: boolean; message: string }; + expect(body.success).toBe(true); + expect(body.message).toBe("other route"); + }); + + it("should apply to sub-paths of the specified path", async () => { + const app = new OpenAPIHono<{ + Variables: { client: any; auth: AuthJwtPayload }; + }>(); + + // Set up auth context first + app.use(env.URL_PATH_PREFIX + "/api/test/sub", async (c, next) => { + c.set("auth", { + sandboxId: "test-sandbox-123", + }); + await next(); + }); + + // Apply the requireSandboxId middleware to the sub-path + setupRequiredSandboxIdMiddleware( + app, + env.URL_PATH_PREFIX + "/api/test/sub" + ); + + // Add the route handler + app.get(env.URL_PATH_PREFIX + "/api/test/sub", (c) => { + const auth = c.get("auth"); + return c.json({ + success: true, + sandboxId: auth?.sandboxId, + path: "sub", + }); + }); + + const res = await app.request(env.URL_PATH_PREFIX + "/api/test/sub"); + + expect(res.status).toBe(200); + const body = (await res.json()) as { + success: boolean; + sandboxId: string; + path: string; + }; + expect(body.success).toBe(true); + expect(body.sandboxId).toBe("test-sandbox-123"); + expect(body.path).toBe("sub"); + }); + }); +}); diff --git a/apps/coderouter/src/middleware/requireSandboxId.ts b/apps/coderouter/src/middleware/requireSandboxId.ts new file mode 100644 index 0000000000..42aa69a572 --- /dev/null +++ b/apps/coderouter/src/middleware/requireSandboxId.ts @@ -0,0 +1,16 @@ +import { LocalHono } from "@/server"; + +export function setupRequiredSandboxIdMiddleware(app: LocalHono, path: string) { + return app.use(path, async (c, next) => { + const auth = c.get("auth"); + if (!auth || !auth.sandboxId) { + return c.json( + { + error: `"sandboxId" is not set in JWT token.`, + }, + 400 + ); + } + await next(); + }); +} diff --git a/apps/coderouter/src/provider/definition/index.test.ts b/apps/coderouter/src/provider/definition/index.test.ts new file mode 100644 index 0000000000..717f736976 --- /dev/null +++ b/apps/coderouter/src/provider/definition/index.test.ts @@ -0,0 +1,320 @@ +import { describe, it, expect } from "bun:test"; +import { + ClientError, + ClientErrorCode, + ClientErrorCodeToStatus, + Client, +} from "./index"; +import { + SandboxFile, + SandboxFileListOutput, + SandboxFileListInput, + SandboxFileReadOutput, + SandboxFileReadInput, + SandboxFileRenameOutput, + SandboxFileRenameInput, + SandboxFileStatOutput, + SandboxFileWriteOutput, + SandboxFileWriteInput, + SandboxFileStatInput, + SandboxFileCopyInput, + SandboxFileCopyOutput, + SandboxFileDeleteInput, + SandboxFileDeleteOutput, + SandboxFileDownloadInput, + SandboxFileDownloadOutput, +} from "./sandbox/file"; +import { + Sandbox, + SandboxPauseInput, + SandboxResumeOutput, + SandboxResumeInput, + SandboxStopOutput, + SandboxStopInput, + SandboxUrlInput, + SandboxUrlOutput, + SandboxPauseOutput, + SandboxCreateInput, + SandboxCreateOutput, + SandboxGetInput, + SandboxGetOutput, +} from "./sandbox"; + +class MockSandboxFile extends SandboxFile { + constructor(protected readonly client: MockClient) { + super(client); + } + + copy(input: SandboxFileCopyInput): Promise { + throw new Error("Method not implemented."); + } + + delete(input: SandboxFileDeleteInput): Promise { + throw new Error("Method not implemented."); + } + + download( + input: SandboxFileDownloadInput + ): Promise { + throw new Error("Method not implemented."); + } + + list(input: SandboxFileListInput): Promise { + throw new Error("Method not implemented."); + } + + read(input: SandboxFileReadInput): Promise { + throw new Error("Method not implemented."); + } + + rename(input: SandboxFileRenameInput): Promise { + throw new Error("Method not implemented."); + } + + stat(input: SandboxFileStatInput): Promise { + throw new Error("Method not implemented."); + } + + write(input: SandboxFileWriteInput): Promise { + throw new Error("Method not implemented."); + } +} + +class MockSandbox extends Sandbox { + public readonly file: SandboxFile; + + constructor(protected readonly client: MockClient) { + super(client); + this.file = new MockSandboxFile(this.client); + } + + create(input: SandboxCreateInput): Promise { + throw new Error("Method not implemented."); + } + + get(input: SandboxGetInput): Promise { + throw new Error("Method not implemented."); + } + + pause(input: SandboxPauseInput): Promise { + throw new Error("Method not implemented."); + } + + resume(input: SandboxResumeInput): Promise { + throw new Error("Method not implemented."); + } + + stop(input: SandboxStopInput): Promise { + throw new Error("Method not implemented."); + } + + url(input: SandboxUrlInput): Promise { + throw new Error("Method not implemented."); + } +} + +// Mock class extending Client to test abstract class coverage +class MockClient extends Client { + public readonly sandbox: MockSandbox; + + constructor() { + super({}); + this.sandbox = new MockSandbox(this); + } + + testMethod() { + return "test"; + } +} + +describe("Client", () => { + it("should allow extending the abstract class", () => { + const mockClient = new MockClient(); + expect(mockClient).toBeInstanceOf(Client); + expect(mockClient).toBeInstanceOf(MockClient); + expect(mockClient.testMethod()).toBe("test"); + }); +}); + +describe("ClientErrorCode", () => { + it("should have the correct error codes", () => { + expect(ClientErrorCode.Unimplemented).toBe(501001); + expect(ClientErrorCode.ImplementationError).toBe(503001); + expect(ClientErrorCode.SandboxNotFound).toBe(404001); + expect(ClientErrorCode.FileNotFound).toBe(404002); + }); + + it("should have bidirectional enum mapping", () => { + // Test that the enum works both ways (number to string and string to number) + expect(ClientErrorCode[501001]).toBe("Unimplemented"); + expect(ClientErrorCode[503001]).toBe("ImplementationError"); + expect(ClientErrorCode[404001]).toBe("SandboxNotFound"); + expect(ClientErrorCode[404002]).toBe("FileNotFound"); + + // Test that the enum values are properly set + expect(ClientErrorCode.Unimplemented).toBe(501001); + expect(ClientErrorCode.ImplementationError).toBe(503001); + expect(ClientErrorCode.SandboxNotFound).toBe(404001); + expect(ClientErrorCode.FileNotFound).toBe(404002); + }); +}); + +describe("ClientErrorCodeToStatus", () => { + it("should map error codes to correct HTTP status codes", () => { + expect(ClientErrorCodeToStatus[ClientErrorCode.Unimplemented]).toBe(501); + expect(ClientErrorCodeToStatus[ClientErrorCode.ImplementationError]).toBe( + 503 + ); + expect(ClientErrorCodeToStatus[ClientErrorCode.SandboxNotFound]).toBe(404); + expect(ClientErrorCodeToStatus[ClientErrorCode.FileNotFound]).toBe(404); + }); + + it("should have all error codes mapped", () => { + // Get only the numeric enum values (filter out string keys) + const errorCodes = Object.values(ClientErrorCode).filter( + (code): code is ClientErrorCode => typeof code === "number" + ); + const mappedCodes = Object.keys(ClientErrorCodeToStatus).map(Number); + + expect(mappedCodes).toHaveLength(errorCodes.length); + errorCodes.forEach((code) => { + expect(ClientErrorCodeToStatus[code]).toBeDefined(); + }); + }); +}); + +describe("ClientError", () => { + it("should create a ClientError with correct properties", () => { + const error = new ClientError( + ClientErrorCode.FileNotFound, + "File not found", + false + ); + + expect(error.code).toBe(ClientErrorCode.FileNotFound); + expect(error.message).toBe("File not found"); + expect(error.retriable).toBe(false); + expect(error.name).toBe("Error"); + expect(error.status).toBe(404); + }); + + it("should extend Error class and call super constructor", () => { + const error = new ClientError( + ClientErrorCode.SandboxNotFound, + "Sandbox not found", + true + ); + + expect(error).toBeInstanceOf(Error); + expect(error).toBeInstanceOf(ClientError); + + // Test that the Error constructor was called with the message + expect(error.message).toBe("Sandbox not found"); + expect(error.name).toBe("Error"); + + // Test that the error stack trace is properly set (indicates super() was called) + expect(error.stack).toBeDefined(); + }); + + it("should have correct status property", () => { + const fileNotFoundError = new ClientError( + ClientErrorCode.FileNotFound, + "File not found", + false + ); + expect(fileNotFoundError.status).toBe(404); + + const unimplementedError = new ClientError( + ClientErrorCode.Unimplemented, + "Not implemented", + false + ); + expect(unimplementedError.status).toBe(501); + + const implementationError = new ClientError( + ClientErrorCode.ImplementationError, + "Implementation error", + true + ); + expect(implementationError.status).toBe(503); + }); + + it("should have correct toString representation", () => { + const error = new ClientError( + ClientErrorCode.ImplementationError, + "Something went wrong", + true + ); + + const expectedString = + "Something went wrong (code: 503001, retriable: true)"; + expect(error.toString()).toBe(expectedString); + }); + + it("should handle different retriable values", () => { + const retriableError = new ClientError( + ClientErrorCode.SandboxNotFound, + "Retry this", + true + ); + expect(retriableError.retriable).toBe(true); + + const nonRetriableError = new ClientError( + ClientErrorCode.FileNotFound, + "Don't retry this", + false + ); + expect(nonRetriableError.retriable).toBe(false); + }); + + it("should handle empty message", () => { + const error = new ClientError(ClientErrorCode.Unimplemented, "", false); + + expect(error.message).toBe(""); + expect(error.toString()).toBe(" (code: 501001, retriable: false)"); + }); + + it("should handle special characters in message", () => { + const error = new ClientError( + ClientErrorCode.ImplementationError, + "Error with special chars: !@#$%^&*()", + true + ); + + expect(error.message).toBe("Error with special chars: !@#$%^&*()"); + expect(error.toString()).toBe( + "Error with special chars: !@#$%^&*() (code: 503001, retriable: true)" + ); + }); + + it("should handle all error code types in constructor", () => { + // Test constructor with each error code to ensure full coverage + const unimplementedError = new ClientError( + ClientErrorCode.Unimplemented, + "Not implemented", + false + ); + expect(unimplementedError.code).toBe(ClientErrorCode.Unimplemented); + + const implementationError = new ClientError( + ClientErrorCode.ImplementationError, + "Implementation error", + true + ); + expect(implementationError.code).toBe(ClientErrorCode.ImplementationError); + + const sandboxNotFoundError = new ClientError( + ClientErrorCode.SandboxNotFound, + "Sandbox not found", + false + ); + expect(sandboxNotFoundError.code).toBe(ClientErrorCode.SandboxNotFound); + + const fileNotFoundError = new ClientError( + ClientErrorCode.FileNotFound, + "File not found", + true + ); + expect(fileNotFoundError.code).toBe(ClientErrorCode.FileNotFound); + }); +}); diff --git a/apps/coderouter/src/provider/definition/index.ts b/apps/coderouter/src/provider/definition/index.ts new file mode 100644 index 0000000000..7ab5d0e912 --- /dev/null +++ b/apps/coderouter/src/provider/definition/index.ts @@ -0,0 +1,48 @@ +import { Sandbox } from "./sandbox"; + +export interface ClientOptions { + sandboxId?: string; + userId?: string; +} + +export abstract class Client { + constructor(public readonly options: ClientOptions) {} + + public abstract readonly sandbox: Sandbox; +} + +export enum ClientErrorCode { + Unimplemented = 501001, + ImplementationError = 503001, + SandboxNotFound = 404001, + FileNotFound = 404002, + MissingSandboxId = 400001, + MissingTemplateId = 400002, +} + +export const ClientErrorCodeToStatus = { + [ClientErrorCode.Unimplemented]: 501, + [ClientErrorCode.ImplementationError]: 503, + [ClientErrorCode.SandboxNotFound]: 404, + [ClientErrorCode.FileNotFound]: 404, + [ClientErrorCode.MissingSandboxId]: 400, + [ClientErrorCode.MissingTemplateId]: 400, +}; + +export class ClientError extends Error { + constructor( + public readonly code: ClientErrorCode, + public readonly message: string, + public readonly retriable: boolean + ) { + super(message); + } + + get status() { + return ClientErrorCodeToStatus[this.code]; + } + + toString() { + return `${this.message} (code: ${this.code}, retriable: ${this.retriable})`; + } +} diff --git a/apps/coderouter/src/provider/definition/sandbox/file/index.ts b/apps/coderouter/src/provider/definition/sandbox/file/index.ts new file mode 100644 index 0000000000..525e202281 --- /dev/null +++ b/apps/coderouter/src/provider/definition/sandbox/file/index.ts @@ -0,0 +1,71 @@ +import { Client } from "../../index"; + +export interface SandboxFileCopyInput {} +export interface SandboxFileCopyOutput {} + +export interface SandboxFileDeleteInput { + path: string; +} +export interface SandboxFileDeleteOutput {} + +export interface SandboxFileDownloadInput {} +export interface SandboxFileDownloadOutput {} + +export interface SandboxFileListInput { + path: string; +} +export interface SandboxFileListOutput { + files: Array<{ + name: string; + path: string; + type: "file" | "directory"; + }>; +} + +export interface SandboxFileReadInput { + path: string; +} +export interface SandboxFileReadOutput { + data: string; +} + +export interface SandboxFileRenameInput { + oldPath: string; + newPath: string; +} +export interface SandboxFileRenameOutput {} + +export interface SandboxFileStatInput { + path: string; +} +export interface SandboxFileStatOutput { + type: "file" | "directory"; +} + +export interface SandboxFileWriteInput { + files: Array<{ + path: string; + data: string; + overwrite: boolean; + }>; +} +export interface SandboxFileWriteOutput {} + +export abstract class SandboxFile { + constructor(protected readonly client: T) {} + + abstract copy(input: SandboxFileCopyInput): Promise; + abstract delete( + input: SandboxFileDeleteInput + ): Promise; + abstract download( + input: SandboxFileDownloadInput + ): Promise; + abstract list(input: SandboxFileListInput): Promise; + abstract read(input: SandboxFileReadInput): Promise; + abstract rename( + input: SandboxFileRenameInput + ): Promise; + abstract stat(input: SandboxFileStatInput): Promise; + abstract write(input: SandboxFileWriteInput): Promise; +} diff --git a/apps/coderouter/src/provider/definition/sandbox/index.ts b/apps/coderouter/src/provider/definition/sandbox/index.ts new file mode 100644 index 0000000000..157fc2258d --- /dev/null +++ b/apps/coderouter/src/provider/definition/sandbox/index.ts @@ -0,0 +1,66 @@ +import { Client } from "../index"; +import { SandboxFile } from "./file"; + +export interface SandboxCreateInput { + // the sandbox ID is fed from the JWT token + // id: string; + // specify your own template ID to start up your sandbox faster + templateId: string; + // customize your sandbox metadata + metadata: Record; +} +export interface SandboxCreateOutput { + externalId: string; +} + +export interface SandboxGetInput { + // the sandbox ID is fed from the JWT token + // id: string; +} +export interface SandboxGetOutput { + id: string; + externalId: string; +} + +export interface SandboxPauseInput { + // the sandbox ID is fed from the JWT token + // id: string; +} +export interface SandboxPauseOutput {} + +export interface SandboxResumeInput { + // the sandbox ID is fed from the JWT token + // id: string; +} +export interface SandboxResumeOutput {} + +export interface SandboxStopInput { + // the sandbox ID is fed from the JWT token + // id: string; +} +export interface SandboxStopOutput {} + +export interface SandboxUrlInput { + // the sandbox ID is fed from the JWT token + // id: string; +} +export interface SandboxUrlOutput { + url: string; +} + +export abstract class Sandbox { + public abstract readonly file: SandboxFile; + + constructor(protected readonly client: T) {} + + // called when the class is instantiated + // use this to resume the sandbox, bump the timeout and/or connect to the sandbox + abstract beforeSandboxCall(): Promise; + + abstract create(input: SandboxCreateInput): Promise; + abstract get(input: SandboxGetInput): Promise; + abstract pause(input: SandboxPauseInput): Promise; + abstract resume(input: SandboxResumeInput): Promise; + abstract stop(input: SandboxStopInput): Promise; + abstract url(input: SandboxUrlInput): Promise; +} diff --git a/apps/coderouter/src/provider/e2b/index.ts b/apps/coderouter/src/provider/e2b/index.ts new file mode 100644 index 0000000000..acb9fa9ab9 --- /dev/null +++ b/apps/coderouter/src/provider/e2b/index.ts @@ -0,0 +1,16 @@ +import { Sandbox as _Sandbox } from "@e2b/code-interpreter"; +import { Client, ClientOptions } from "../definition"; +import { E2BSandbox } from "./sandbox"; + +export class E2BClient extends Client { + public readonly sandbox: E2BSandbox; + + // property is initialized by the sandbox class + // this is the E2B sandbox instance + _sandbox?: _Sandbox; + + constructor(options: ClientOptions, public readonly apiKey: string) { + super(options); + this.sandbox = new E2BSandbox(this); + } +} diff --git a/apps/coderouter/src/provider/e2b/sandbox/file/index.test.ts b/apps/coderouter/src/provider/e2b/sandbox/file/index.test.ts new file mode 100644 index 0000000000..74460b91c8 --- /dev/null +++ b/apps/coderouter/src/provider/e2b/sandbox/file/index.test.ts @@ -0,0 +1,490 @@ +import { describe, expect, it, beforeEach, jest } from "bun:test"; +import { E2BClient } from "@/provider/e2b/index"; +import { E2BSandboxFile } from "./index"; +import { + ClientError, + ClientErrorCode, + ClientOptions, +} from "@/provider/definition"; + +// Create a mock sandbox object that matches the expected interface +const createMockSandbox = () => + ({ + files: { + exists: jest.fn(), + read: jest.fn(), + write: jest.fn(), + remove: jest.fn(), + rename: jest.fn(), + list: jest.fn(), + getInfo: jest.fn(), + }, + create: jest.fn(), + kill: jest.fn(), + // Add other required properties to satisfy the Sandbox interface + runCode: jest.fn(), + createCodeContext: jest.fn(), + jupyterUrl: "http://localhost:8888", + commands: jest.fn(), + sandboxId: "test-sandbox-id", + process: { pid: 123 }, + close: jest.fn(), + restart: jest.fn(), + getLogs: jest.fn(), + getProcessLogs: jest.fn(), + getStdout: jest.fn(), + getStderr: jest.fn(), + getExitCode: jest.fn(), + getStatus: jest.fn(), + getMetadata: jest.fn(), + updateMetadata: jest.fn(), + getEnvironmentVariables: jest.fn(), + updateEnvironmentVariables: jest.fn(), + getPorts: jest.fn(), + getOpenPorts: jest.fn(), + getOpenPort: jest.fn(), + getPort: jest.fn(), + openPort: jest.fn(), + closePort: jest.fn(), + getHostname: jest.fn(), + getUrl: jest.fn(), + getLocalUrl: jest.fn(), + getPublicUrl: jest.fn(), + getLocalPort: jest.fn(), + getPublicPort: jest.fn(), + getLocalHostname: jest.fn(), + getPublicHostname: jest.fn(), + getLocalProtocol: jest.fn(), + getPublicProtocol: jest.fn(), + getLocalScheme: jest.fn(), + getPublicScheme: jest.fn(), + getLocalAuthority: jest.fn(), + getPublicAuthority: jest.fn(), + getLocalPath: jest.fn(), + getPublicPath: jest.fn(), + getLocalQuery: jest.fn(), + getPublicQuery: jest.fn(), + getLocalFragment: jest.fn(), + getPublicFragment: jest.fn(), + getLocalUserInfo: jest.fn(), + getPublicUserInfo: jest.fn(), + getLocalUsername: jest.fn(), + getPublicUsername: jest.fn(), + getLocalPassword: jest.fn(), + getPublicPassword: jest.fn(), + getLocalHost: jest.fn(), + getPublicHost: jest.fn(), + getLocalPortNumber: jest.fn(), + getPublicPortNumber: jest.fn(), + getLocalHostnameString: jest.fn(), + getPublicHostnameString: jest.fn(), + getLocalProtocolString: jest.fn(), + getPublicProtocolString: jest.fn(), + getLocalSchemeString: jest.fn(), + getPublicSchemeString: jest.fn(), + getLocalAuthorityString: jest.fn(), + getPublicAuthorityString: jest.fn(), + getLocalPathString: jest.fn(), + getPublicPathString: jest.fn(), + getLocalQueryString: jest.fn(), + getPublicQueryString: jest.fn(), + getLocalFragmentString: jest.fn(), + getPublicFragmentString: jest.fn(), + getLocalUserInfoString: jest.fn(), + getPublicUserInfoString: jest.fn(), + getLocalUsernameString: jest.fn(), + getPublicUsernameString: jest.fn(), + getLocalPasswordString: jest.fn(), + getPublicPasswordString: jest.fn(), + getLocalHostString: jest.fn(), + getPublicHostString: jest.fn(), + getLocalPortNumberString: jest.fn(), + getPublicPortNumberString: jest.fn(), + getLocalHostnameNumber: jest.fn(), + getPublicHostnameNumber: jest.fn(), + getLocalProtocolNumber: jest.fn(), + getPublicProtocolNumber: jest.fn(), + getLocalSchemeNumber: jest.fn(), + getPublicSchemeNumber: jest.fn(), + getLocalAuthorityNumber: jest.fn(), + getPublicAuthorityNumber: jest.fn(), + getLocalPathNumber: jest.fn(), + getPublicPathNumber: jest.fn(), + getLocalQueryNumber: jest.fn(), + getPublicQueryNumber: jest.fn(), + getLocalFragmentNumber: jest.fn(), + getPublicFragmentNumber: jest.fn(), + getLocalUserInfoNumber: jest.fn(), + getPublicUserInfoNumber: jest.fn(), + getLocalUsernameNumber: jest.fn(), + getPublicUsernameNumber: jest.fn(), + getLocalPasswordNumber: jest.fn(), + getPublicPasswordNumber: jest.fn(), + getLocalHostNumber: jest.fn(), + getPublicHostNumber: jest.fn(), + getLocalPortNumberNumber: jest.fn(), + getPublicPortNumberNumber: jest.fn(), + } as any); + +class E2BMockClient extends E2BClient { + private _mockSandbox = createMockSandbox(); + + constructor(options: ClientOptions) { + super(options, "test-api-key"); + } + + _sandbox = this._mockSandbox; + + // Method to set sandbox to null for testing error cases + setSandboxNull() { + this._mockSandbox = null as any; + this._sandbox = null as any; + } + + // Method to restore sandbox for testing + restoreSandbox() { + this._mockSandbox = createMockSandbox(); + this._sandbox = this._mockSandbox; + } +} + +describe("E2BSandboxFile", () => { + let mockClient: E2BMockClient; + let sandboxFile: E2BSandboxFile; + + beforeEach(() => { + mockClient = new E2BMockClient({ sandboxId: "test-sandbox-id" }); + sandboxFile = new E2BSandboxFile(mockClient); + jest.clearAllMocks(); + }); + + describe("copy", () => { + it("should throw Unimplemented error", async () => { + await expect( + sandboxFile.copy({ oldPath: "/old", newPath: "/new" }) + ).rejects.toThrow( + new ClientError(ClientErrorCode.Unimplemented, "Not implemented", false) + ); + }); + }); + + describe("delete", () => { + it("should delete file successfully", async () => { + const input = { path: "/test/file.txt" }; + + await sandboxFile.delete(input); + + expect(mockClient._sandbox.files.remove).toHaveBeenCalledTimes(1); + expect(mockClient._sandbox.files.remove).toHaveBeenCalledWith( + "/test/file.txt" + ); + }); + + it("should throw error when sandbox is not instantiated", async () => { + mockClient.setSandboxNull(); + + const input = { path: "/test/file.txt" }; + + await expect(sandboxFile.delete(input)).rejects.toThrow( + new ClientError( + ClientErrorCode.ImplementationError, + "Sandbox is not instantiated. Call start() or resume() first.", + false + ) + ); + + // Note: We can't check mockClient._sandbox.files.remove since sandbox is null + }); + }); + + describe("download", () => { + it("should throw Unimplemented error", async () => { + await expect( + sandboxFile.download({ path: "/test/file.txt" }) + ).rejects.toThrow( + new ClientError(ClientErrorCode.Unimplemented, "Not implemented", false) + ); + }); + }); + + describe("list", () => { + it("should list files successfully", async () => { + const mockFiles = [ + { name: "file1.txt", path: "/dir/file1.txt", type: "file" }, + { name: "subdir", path: "/dir/subdir", type: "directory" }, + ]; + + (mockClient._sandbox.files.list as any).mockResolvedValue(mockFiles); + + const input = { path: "/dir" }; + const result = await sandboxFile.list(input); + + expect(mockClient._sandbox.files.list).toHaveBeenCalledTimes(1); + expect(mockClient._sandbox.files.list).toHaveBeenCalledWith("/dir"); + expect(result).toEqual({ + files: [ + { name: "file1.txt", path: "/dir/file1.txt", type: "file" }, + { name: "subdir", path: "/dir/subdir", type: "directory" }, + ], + }); + }); + + it("should throw error when sandbox is not instantiated", async () => { + mockClient.setSandboxNull(); + + const input = { path: "/dir" }; + + await expect(sandboxFile.list(input)).rejects.toThrow( + new ClientError( + ClientErrorCode.ImplementationError, + "Sandbox is not instantiated. Call start() or resume() first.", + false + ) + ); + }); + }); + + describe("read", () => { + it("should read file successfully", async () => { + const mockData = "file content"; + (mockClient._sandbox.files.read as any).mockResolvedValue(mockData); + + const input = { path: "/test/file.txt" }; + const result = await sandboxFile.read(input); + + expect(mockClient._sandbox.files.read).toHaveBeenCalledTimes(1); + expect(mockClient._sandbox.files.read).toHaveBeenCalledWith( + "/test/file.txt" + ); + expect(result).toEqual({ data: mockData }); + }); + + it("should throw FileNotFound error when file doesn't exist", async () => { + (mockClient._sandbox.files.read as any).mockResolvedValue(null); + + const input = { path: "/test/file.txt" }; + + await expect(sandboxFile.read(input)).rejects.toThrow( + new ClientError(ClientErrorCode.FileNotFound, "File not found", false) + ); + + expect(mockClient._sandbox.files.read).toHaveBeenCalledTimes(1); + expect(mockClient._sandbox.files.read).toHaveBeenCalledWith( + "/test/file.txt" + ); + }); + + it("should throw error when sandbox is not instantiated", async () => { + mockClient.setSandboxNull(); + + const input = { path: "/test/file.txt" }; + + await expect(sandboxFile.read(input)).rejects.toThrow( + new ClientError( + ClientErrorCode.ImplementationError, + "Sandbox is not instantiated. Call start() or resume() first.", + false + ) + ); + }); + }); + + describe("rename", () => { + it("should rename file successfully", async () => { + const input = { oldPath: "/old/file.txt", newPath: "/new/file.txt" }; + + await sandboxFile.rename(input); + + expect(mockClient._sandbox.files.rename).toHaveBeenCalledTimes(1); + expect(mockClient._sandbox.files.rename).toHaveBeenCalledWith( + "/old/file.txt", + "/new/file.txt" + ); + }); + + it("should throw error when sandbox is not instantiated", async () => { + mockClient.setSandboxNull(); + + const input = { oldPath: "/old/file.txt", newPath: "/new/file.txt" }; + + await expect(sandboxFile.rename(input)).rejects.toThrow( + new ClientError( + ClientErrorCode.ImplementationError, + "Sandbox is not instantiated. Call start() or resume() first.", + false + ) + ); + }); + }); + + describe("stat", () => { + it("should get file info successfully for file", async () => { + const mockFileInfo = { type: "file" }; + (mockClient._sandbox.files.getInfo as any).mockResolvedValue( + mockFileInfo + ); + + const input = { path: "/test/file.txt" }; + const result = await sandboxFile.stat(input); + + expect(mockClient._sandbox.files.getInfo).toHaveBeenCalledTimes(1); + expect(mockClient._sandbox.files.getInfo).toHaveBeenCalledWith( + "/test/file.txt" + ); + expect(result).toEqual({ type: "file" }); + }); + + it("should get file info successfully for directory", async () => { + const mockFileInfo = { type: "directory" }; + (mockClient._sandbox.files.getInfo as any).mockResolvedValue( + mockFileInfo + ); + + const input = { path: "/test/dir" }; + const result = await sandboxFile.stat(input); + + expect(mockClient._sandbox.files.getInfo).toHaveBeenCalledTimes(1); + expect(mockClient._sandbox.files.getInfo).toHaveBeenCalledWith( + "/test/dir" + ); + expect(result).toEqual({ type: "directory" }); + }); + + it("should throw FileNotFound error when file doesn't exist", async () => { + (mockClient._sandbox.files.getInfo as any).mockResolvedValue(null); + + const input = { path: "/test/file.txt" }; + + await expect(sandboxFile.stat(input)).rejects.toThrow( + new ClientError(ClientErrorCode.FileNotFound, "File not found", false) + ); + + expect(mockClient._sandbox.files.getInfo).toHaveBeenCalledTimes(1); + expect(mockClient._sandbox.files.getInfo).toHaveBeenCalledWith( + "/test/file.txt" + ); + }); + + it("should throw error when sandbox is not instantiated", async () => { + mockClient.setSandboxNull(); + + const input = { path: "/test/file.txt" }; + + await expect(sandboxFile.stat(input)).rejects.toThrow( + new ClientError( + ClientErrorCode.ImplementationError, + "Sandbox is not instantiated. Call start() or resume() first.", + false + ) + ); + }); + }); + + describe("write", () => { + it("should write single file successfully", async () => { + const input = { + files: [ + { + path: "/test/file.txt", + data: "file content", + overwrite: true, + }, + ], + }; + + await sandboxFile.write(input); + + expect(mockClient._sandbox.files.write).toHaveBeenCalledTimes(1); + expect(mockClient._sandbox.files.write).toHaveBeenCalledWith([ + { path: "/test/file.txt", data: "file content" }, + ]); + }); + + it("should write multiple files successfully", async () => { + const input = { + files: [ + { path: "/test/file1.txt", data: "content1", overwrite: true }, + { path: "/test/file2.txt", data: "content2", overwrite: false }, + ], + }; + + await sandboxFile.write(input); + + expect(mockClient._sandbox.files.write).toHaveBeenCalledTimes(1); + expect(mockClient._sandbox.files.write).toHaveBeenCalledWith([ + { path: "/test/file1.txt", data: "content1" }, + { path: "/test/file2.txt", data: "content2" }, + ]); + }); + + it("should skip existing files when overwrite is false", async () => { + (mockClient._sandbox.files.exists as any).mockResolvedValue(true); + + const input = { + files: [ + { + path: "/test/file.txt", + data: "file content", + overwrite: false, + }, + ], + }; + + await sandboxFile.write(input); + + expect(mockClient._sandbox.files.exists).toHaveBeenCalledTimes(1); + expect(mockClient._sandbox.files.exists).toHaveBeenCalledWith( + "/test/file.txt" + ); + expect(mockClient._sandbox.files.write).toHaveBeenCalledTimes(1); + expect(mockClient._sandbox.files.write).toHaveBeenCalledWith([]); + }); + + it("should write file when overwrite is false and file doesn't exist", async () => { + (mockClient._sandbox.files.exists as any).mockResolvedValue(false); + + const input = { + files: [ + { + path: "/test/file.txt", + data: "file content", + overwrite: false, + }, + ], + }; + + await sandboxFile.write(input); + + expect(mockClient._sandbox.files.exists).toHaveBeenCalledTimes(1); + expect(mockClient._sandbox.files.exists).toHaveBeenCalledWith( + "/test/file.txt" + ); + expect(mockClient._sandbox.files.write).toHaveBeenCalledTimes(1); + expect(mockClient._sandbox.files.write).toHaveBeenCalledWith([ + { path: "/test/file.txt", data: "file content" }, + ]); + }); + + it("should throw error when sandbox is not instantiated", async () => { + mockClient.setSandboxNull(); + + const input = { + files: [ + { + path: "/test/file.txt", + data: "file content", + overwrite: true, + }, + ], + }; + + await expect(sandboxFile.write(input)).rejects.toThrow( + new ClientError( + ClientErrorCode.ImplementationError, + "Sandbox is not instantiated. Call start() or resume() first.", + false + ) + ); + }); + }); +}); diff --git a/apps/coderouter/src/provider/e2b/sandbox/file/index.ts b/apps/coderouter/src/provider/e2b/sandbox/file/index.ts new file mode 100644 index 0000000000..c96eee5982 --- /dev/null +++ b/apps/coderouter/src/provider/e2b/sandbox/file/index.ts @@ -0,0 +1,203 @@ +import { ClientError, ClientErrorCode } from "@/provider/definition"; +import { E2BClient } from "../.."; +import { + SandboxFile, + SandboxFileCopyInput, + SandboxFileCopyOutput, + SandboxFileDeleteInput, + SandboxFileDeleteOutput, + SandboxFileDownloadInput, + SandboxFileDownloadOutput, + SandboxFileListInput, + SandboxFileListOutput, + SandboxFileReadInput, + SandboxFileReadOutput, + SandboxFileRenameInput, + SandboxFileRenameOutput, + SandboxFileStatInput, + SandboxFileStatOutput, + SandboxFileWriteOutput, + SandboxFileWriteInput, +} from "../../../definition/sandbox/file"; +import { NotFoundError } from "@e2b/code-interpreter"; +import path from "path"; + +export class E2BSandboxFile extends SandboxFile { + // the folder to store the files in the sandbox + // when creating a new template, the code must be stored in this folder + protected folder: string = "/code"; + + constructor(client: E2BClient) { + super(client); + } + + async copy(input: SandboxFileCopyInput): Promise { + throw new ClientError( + ClientErrorCode.Unimplemented, + "Not implemented", + false + ); + } + + async delete( + input: SandboxFileDeleteInput + ): Promise { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + "Sandbox is not instantiated. Call start() or resume() first.", + false + ); + } + await this.client._sandbox.files.remove(this.fullpath(input.path)); + return {}; + } + + async download( + input: SandboxFileDownloadInput + ): Promise { + throw new ClientError( + ClientErrorCode.Unimplemented, + "Not implemented", + false + ); + } + + async list(input: SandboxFileListInput): Promise { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + "Sandbox is not instantiated. Call start() or resume() first.", + false + ); + } + try { + const p = path.normalize(this.fullpath(input.path)); + const files = await this.client._sandbox.files.list(p); + return { + files: files.map((file) => ({ + name: file.name, + path: file.path.replace(this.folder, ""), + type: file.type === "file" ? "file" : "directory", + })), + }; + } catch (e) { + if (e instanceof NotFoundError) { + throw new ClientError( + ClientErrorCode.FileNotFound, + "Folder or file not found", + false + ); + } + throw e; + } + } + + async read(input: SandboxFileReadInput): Promise { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + "Sandbox is not instantiated. Call start() or resume() first.", + false + ); + } + try { + const data = await this.client._sandbox.files.read( + this.fullpath(input.path) + ); + if (!data) { + throw new ClientError( + ClientErrorCode.FileNotFound, + "File not found", + false + ); + } + + return { + data, + }; + } catch (e) { + if (e instanceof NotFoundError) { + throw new ClientError( + ClientErrorCode.FileNotFound, + "File not found", + false + ); + } + throw e; + } + } + + async rename( + input: SandboxFileRenameInput + ): Promise { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + "Sandbox is not instantiated. Call start() or resume() first.", + false + ); + } + await this.client._sandbox.files.rename( + this.fullpath(input.oldPath), + this.fullpath(input.newPath) + ); + return {}; + } + + async stat(input: SandboxFileStatInput): Promise { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + "Sandbox is not instantiated. Call start() or resume() first.", + false + ); + } + const file = await this.client._sandbox.files.getInfo( + this.fullpath(input.path) + ); + if (!file) { + throw new ClientError( + ClientErrorCode.FileNotFound, + "File not found", + false + ); + } + return { + type: file.type === "file" ? "file" : "directory", + }; + } + + async write(input: SandboxFileWriteInput): Promise { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + "Sandbox is not instantiated. Call start() or resume() first.", + false + ); + } + const files: { path: string; data: string }[] = []; + for (const file of Array.isArray(input.files) + ? input.files + : [input.files]) { + if (!file.overwrite) { + const exists = await this.client._sandbox.files.exists( + this.fullpath(file.path) + ); + if (exists) { + continue; + } + } + files.push({ + path: this.fullpath(file.path), + data: file.data, + }); + } + await this.client._sandbox.files.write(files); + return {}; + } + + protected fullpath(path: string): string { + return this.folder + (path.startsWith("/") ? "" : "/") + path; + } +} diff --git a/apps/coderouter/src/provider/e2b/sandbox/index.ts b/apps/coderouter/src/provider/e2b/sandbox/index.ts new file mode 100644 index 0000000000..5931e71652 --- /dev/null +++ b/apps/coderouter/src/provider/e2b/sandbox/index.ts @@ -0,0 +1,136 @@ +import { E2BClient } from '../index'; +import { + Sandbox, + SandboxCreateInput, + SandboxCreateOutput, + SandboxGetInput, + SandboxGetOutput, + SandboxPauseInput, + SandboxPauseOutput, + SandboxResumeInput, + SandboxResumeOutput, + SandboxStopInput, + SandboxStopOutput, + SandboxUrlInput, + SandboxUrlOutput, +} from '@/provider/definition/sandbox'; +import { Sandbox as _E2BSandbox } from '@e2b/code-interpreter'; +import { ClientError, ClientErrorCode } from '@/provider/definition'; +import { E2BSandboxFile } from './file'; + +export class E2BSandbox extends Sandbox { + public readonly file: E2BSandboxFile; + + constructor(protected readonly client: E2BClient) { + super(client); + this.file = new E2BSandboxFile(this.client); + } + + async beforeSandboxCall(): Promise { + const e2bSandboxId = (await this.get({})).externalId; + this.client._sandbox = await _E2BSandbox.connect(e2bSandboxId); + // bump the timeout to 5 minutes + this.client._sandbox.setTimeout(1000 * 60 * 5); + } + + async create(input: SandboxCreateInput): Promise { + const sandboxId = this.client.options.sandboxId; + if (!sandboxId) { + throw new ClientError( + ClientErrorCode.MissingSandboxId, + 'Sandbox ID is not set. Please provide a sandbox ID in the JWT token.', + false, + ); + } + if (!input.templateId) { + throw new ClientError( + ClientErrorCode.MissingTemplateId, + 'Template ID is not set. Please provide a template ID.', + false, + ); + } + const metadata: Record = { + sandboxId, + }; + if (this.client.options.userId) { + metadata['userId'] = this.client.options.userId; + } + this.client._sandbox = await _E2BSandbox.create(input.templateId, { + apiKey: this.client.apiKey, + metadata, + }); + return { + externalId: this.client._sandbox.sandboxId, + }; + } + + async get(input: SandboxGetInput): Promise { + if (!this.client.options.sandboxId) { + throw new ClientError( + ClientErrorCode.MissingSandboxId, + 'Sandbox ID is not set. Please provide a sandbox ID in the JWT token.', + false, + ); + } + const query: { metadata: Record } = { + metadata: { sandboxId: this.client.options.sandboxId }, + }; + if (this.client.options.userId) { + query.metadata['userId'] = this.client.options.userId; + } + const list = await _E2BSandbox.list({ query }); + const e2bSandbox = list?.[0]; + if (!e2bSandbox) { + throw new ClientError( + ClientErrorCode.SandboxNotFound, + 'Sandbox not found. Please create a sandbox first.', + false, + ); + } + if (list.length > 1) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Multiple sandboxes found. The system should not create multiple sandboxes with the same sandbox ID.', + false, + ); + } + return { + id: this.client.options.sandboxId, + externalId: e2bSandbox.sandboxId, + }; + } + + async pause(input: SandboxPauseInput): Promise { + return {}; + } + + async resume(input: SandboxResumeInput): Promise { + return {}; + } + + async stop(input: SandboxStopInput): Promise { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ); + } + await this.client._sandbox.kill(); + return {}; + } + + async url(input: SandboxUrlInput): Promise { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ); + } + const url = await this.client._sandbox.getHost(8084); + return { + url: url.startsWith('http') ? url : `https://${url}`, + }; + } +} diff --git a/apps/coderouter/src/provider/index.ts b/apps/coderouter/src/provider/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/coderouter/src/server.ts b/apps/coderouter/src/server.ts new file mode 100644 index 0000000000..acdb46423c --- /dev/null +++ b/apps/coderouter/src/server.ts @@ -0,0 +1,97 @@ +import 'dotenv/config'; +import { logger } from 'hono/logger'; +import { OpenAPIHono } from '@hono/zod-openapi'; +import { api_sandbox_create } from './api/sandbox/create'; +import { api_sandbox_file_copy } from './api/sandbox/file/copy'; +import { api_sandbox_file_delete } from './api/sandbox/file/delete'; +import { api_sandbox_file_download } from './api/sandbox/file/download'; +import { api_sandbox_file_list } from './api/sandbox/file/list'; +import { api_sandbox_file_read } from './api/sandbox/file/read'; +import { api_sandbox_file_rename } from './api/sandbox/file/rename'; +import { api_sandbox_file_stat } from './api/sandbox/file/stat'; +import { api_sandbox_file_write } from './api/sandbox/file/write'; +import { api_sandbox_pause } from './api/sandbox/pause'; +import { api_sandbox_resume } from './api/sandbox/resume'; +import { api_sandbox_stop } from './api/sandbox/stop'; +import { api_sandbox_url } from './api/sandbox/url'; +import { Client } from './provider/definition'; +import { setupClientMiddleware } from './middleware/client'; +import { api_auth_sign } from './api/auth/sign'; +import { AuthJwtPayload } from './util/auth'; +import { setupAuthJwtMiddleware } from './middleware/auth'; +import { setupRequiredSandboxIdMiddleware } from './middleware/requireSandboxId'; +import { env, serve } from 'bun'; +import { setupBeforeSandboxCallMiddleware } from './middleware/beforeSandboxCall'; +import { setupErrorMiddleware } from './middleware/error'; + +export interface Variables { + client: Client; + auth: AuthJwtPayload; +} + +export type LocalHono = OpenAPIHono<{ Variables: Variables }>; + +const app: LocalHono = new OpenAPIHono<{ Variables: Variables }>(); + +app.openAPIRegistry.registerComponent('securitySchemes', 'apikey', { + type: 'http', + scheme: 'bearer', + bearerFormat: 'apikey', +}); + +app.openAPIRegistry.registerComponent('securitySchemes', 'jwt', { + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', +}); + +const basePath = env.URL_PATH_PREFIX + '/api'; + +// must be first +setupErrorMiddleware(app); + +// /auth/sign is not protected by this middleware +setupAuthJwtMiddleware(app, basePath + '/sandbox/*'); +// sandbox ID is required for all sandbox routes +setupRequiredSandboxIdMiddleware(app, basePath + '/sandbox/*'); +// must be last +setupClientMiddleware(app, basePath + '/sandbox/*'); + +setupBeforeSandboxCallMiddleware(app, basePath + '/sandbox/pause'); +setupBeforeSandboxCallMiddleware(app, basePath + '/sandbox/resume'); +setupBeforeSandboxCallMiddleware(app, basePath + '/sandbox/stop'); +setupBeforeSandboxCallMiddleware(app, basePath + '/sandbox/url'); +setupBeforeSandboxCallMiddleware(app, basePath + '/sandbox/file/*'); + +// auth routes +api_auth_sign(app); + +// sandbox routes +api_sandbox_create(app); +api_sandbox_pause(app); +api_sandbox_resume(app); +api_sandbox_stop(app); +api_sandbox_url(app); + +// sandbox file routes +api_sandbox_file_copy(app); +api_sandbox_file_delete(app); +api_sandbox_file_download(app); +api_sandbox_file_list(app); +api_sandbox_file_read(app); +api_sandbox_file_rename(app); +api_sandbox_file_stat(app); +api_sandbox_file_write(app); + +app.doc('/openapi.json', { + openapi: '3.0.0', + info: { + version: '1.0.0', + title: 'Coderouter', + }, +}); + +export default { + fetch: app.fetch, + port: 4444, +}; diff --git a/apps/coderouter/src/util/auth.ts b/apps/coderouter/src/util/auth.ts new file mode 100644 index 0000000000..11333d24ec --- /dev/null +++ b/apps/coderouter/src/util/auth.ts @@ -0,0 +1,41 @@ +import { env } from "bun"; +import jwt from "jsonwebtoken"; +import { z } from "@hono/zod-openapi"; + +export const JwtAuthResponses = { + 401: { + content: { + "application/json": { + schema: z.object({ error: z.string() }), + }, + }, + description: "The JWT token is missing or is invalid.", + }, +}; + +export interface AuthJwtPayload { + sandboxId?: string; + userId?: string; +} + +export function encodeJwtToken(jwtPayload: AuthJwtPayload) { + return jwt.sign(jwtPayload, env.JWT_SECRET_KEY!, { + expiresIn: "1d", + }); +} + +export function decodeJwtToken(token: string) { + return jwt.verify(token, env.JWT_SECRET_KEY!); +} + +export function verifyApiKeyFromHeader(header?: string) { + const apiKey = header?.toLowerCase().split("bearer ")[1]; + if (!apiKey) { + return false; + } + return verifyApiKey(apiKey); +} + +export function verifyApiKey(apiKey: string) { + return apiKey === env.SUPAROUTA_API_KEY; +} diff --git a/apps/coderouter/tsconfig.json b/apps/coderouter/tsconfig.json new file mode 100644 index 0000000000..a8b59d97f2 --- /dev/null +++ b/apps/coderouter/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["ES2023"], + "types": ["@types/node"], + "strict": true, + "noUncheckedIndexedAccess": true, + "skipLibCheck": true, + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + }, + "include": ["src", "scripts", "drizzle.config.ts"] +} diff --git a/apps/coderouter/typedoc.json b/apps/coderouter/typedoc.json new file mode 100644 index 0000000000..8948f7d3a4 --- /dev/null +++ b/apps/coderouter/typedoc.json @@ -0,0 +1,5 @@ +{ + "entryPoints": ["src"], + "out": "site/typedoc", + "tsconfig": "tsconfig.json" +} diff --git a/apps/docker-compose.yaml b/apps/docker-compose.yaml new file mode 100644 index 0000000000..a6b24b06b1 --- /dev/null +++ b/apps/docker-compose.yaml @@ -0,0 +1,25 @@ +# Meant for local development only. +# Secret keys used here are not used in production and are only set for convenience. +name: onlook-web +services: + coderouter: + container_name: coderouter + build: + context: ./coderouter + dockerfile: Dockerfile.dev + ports: + - '4444:4444' + volumes: + - ./coderouter:/app + command: [/usr/local/bin/bun/bin/bun, dev] + nginx: + container_name: nginx + restart: always + image: nginx:1.27.4-alpine + ports: + - '443:443' + volumes: + - ./nginx/conf.d/server.conf:/etc/nginx/conf.d/server.conf:ro + - ./nginx/ssl/onlook-internal.crt:/etc/nginx/ssl/onlook-internal.crt:ro + - ./nginx/ssl/onlook-internal.key:/etc/nginx/ssl/onlook-internal.key:ro + command: [nginx, '-g', 'daemon off;'] diff --git a/apps/nginx/bin/genkeys.sh b/apps/nginx/bin/genkeys.sh new file mode 100644 index 0000000000..ad1771e3c8 --- /dev/null +++ b/apps/nginx/bin/genkeys.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./ssl/onlook-internal.key -out ./ssl/onlook-internal.crt -config ./ssl/openssl.cnf + +# Add the certificate to the keychain – works on Mac +if command -v security &> /dev/null; then + security add-trusted-cert -d -r trustRoot -k ~/Library/Keychains/login.keychain-db ./ssl/onlook-internal.crt +else + echo "Warning: security is not installed" +fi diff --git a/apps/nginx/conf.d/server.conf b/apps/nginx/conf.d/server.conf new file mode 100644 index 0000000000..093a98602b --- /dev/null +++ b/apps/nginx/conf.d/server.conf @@ -0,0 +1,43 @@ +# Main server block for HTTPS +server { + listen 443 ssl; + http2 on; + server_name onlook.internal *.onlook.internal; + + # SSL configuration + ssl_certificate /etc/nginx/ssl/onlook-internal.crt; + ssl_certificate_key /etc/nginx/ssl/onlook-internal.key; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384; + ssl_prefer_server_ciphers off; + + # Common proxy settings + # proxy_connect_timeout 0.25s; + # proxy_send_timeout 0.25s; + # proxy_read_timeout 0.25s; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + + # WebSocket support + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + location /coderouter { + proxy_pass http://host.docker.internal:4444; + } + + location / { + proxy_pass http://host.docker.internal:3000; + } +} + +# HTTP to HTTPS redirect +server { + listen 80; + server_name onlook.internal *.onlook.internal; + return 301 https://$server_name$request_uri; +} diff --git a/apps/nginx/ssl/README.md b/apps/nginx/ssl/README.md new file mode 100644 index 0000000000..d3f4dbc5eb --- /dev/null +++ b/apps/nginx/ssl/README.md @@ -0,0 +1 @@ +Do not use this private and public key in prod. Those are designed for local usage only. diff --git a/apps/nginx/ssl/onlook-internal.crt b/apps/nginx/ssl/onlook-internal.crt new file mode 100644 index 0000000000..c9714bea58 --- /dev/null +++ b/apps/nginx/ssl/onlook-internal.crt @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEBDCCAuygAwIBAgIURypApXyi6zML9QoB5/0BYkPr5TowDQYJKoZIhvcNAQEL +BQAwgZIxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH +DA1TYW4gRnJhbmNpc2NvMQ8wDQYDVQQKDAZPbmxvb2sxDDAKBgNVBAsMA0VuZzEY +MBYGA1UEAwwPb25sb29rLmludGVybmFsMR0wGwYJKoZIhvcNAQkBFg5lbmdAb25s +b29rLmNvbTAeFw0yNTA4MDcxNzQ0NDVaFw0yNjA4MDcxNzQ0NDVaMIGSMQswCQYD +VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5j +aXNjbzEPMA0GA1UECgwGT25sb29rMQwwCgYDVQQLDANFbmcxGDAWBgNVBAMMD29u +bG9vay5pbnRlcm5hbDEdMBsGCSqGSIb3DQEJARYOZW5nQG9ubG9vay5jb20wggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCkpqbfeXnSorbz8ciQ0q5ydN2 +UDsK6eqnJDXE6FO0WpbQlWEApORFML59zELaPx2sALTfnjfJuwFREuerSwMLqAl7 +UfHWrGtur1Sn99Xr2/gjToOnlIew0IY8t9hmY7Gg0lTlU1Xf7lNGjyiDj03prBQh +5txa2SzeqewmMbPKQ6hdac4kjga55UQP29MpPgNjW7HIr3SherR8JHKv03ZvnRR+ +SXJ00znocEbQaCLBuSO7ZNj3oNT0pn3WuhLTj47breaCC8mWADbYC1Ltss5tIKPT +ogjRqxkZbm3sfd2cq1HL0x4FwlboLPt9wo5UvlOs/qts1iP8XVKTy/uj9bcbAgMB +AAGjUDBOMC0GA1UdEQQmMCSCD29ubG9vay5pbnRlcm5hbIIRKi5vbmxvb2suaW50 +ZXJuYWwwHQYDVR0OBBYEFB3RhNcRNIcIqjVyhsEL2DFQtNLUMA0GCSqGSIb3DQEB +CwUAA4IBAQCWz0xa40patIChU0I4Y1MqfJ1ic+iy7iP8aLmSRdoDp/8JSf7LYpnd +I9NRGKtpQp6uv5bmGMoK8A+J1BfZp2TZ0dMxsIEANEYYiq9aej095isQueFaK2s7 +6ji4pR3ahhljdcwpsMDYicdJBnkl9xXWlaGxXb0ZerYfPh4+e0CP/5hJNQDZttww +sFe1ksupoWzKWJIYB80S+HDQo7FvHvTXm+VOqfNTIxait3I+KEnt8ovRbSu4PsMw +kAdvwDhb7QBxEIriZgxjgLEX0XaGuC84oA0fttjFuwdhOhy4o5aBnhUsWWFdHPoi +4vdIjPk+l06LeK3YdploPhnVv6ATpavZ +-----END CERTIFICATE----- diff --git a/apps/nginx/ssl/onlook-internal.key b/apps/nginx/ssl/onlook-internal.key new file mode 100644 index 0000000000..1fe143d2b8 --- /dev/null +++ b/apps/nginx/ssl/onlook-internal.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDCkpqbfeXnSorb +z8ciQ0q5ydN2UDsK6eqnJDXE6FO0WpbQlWEApORFML59zELaPx2sALTfnjfJuwFR +EuerSwMLqAl7UfHWrGtur1Sn99Xr2/gjToOnlIew0IY8t9hmY7Gg0lTlU1Xf7lNG +jyiDj03prBQh5txa2SzeqewmMbPKQ6hdac4kjga55UQP29MpPgNjW7HIr3SherR8 +JHKv03ZvnRR+SXJ00znocEbQaCLBuSO7ZNj3oNT0pn3WuhLTj47breaCC8mWADbY +C1Ltss5tIKPTogjRqxkZbm3sfd2cq1HL0x4FwlboLPt9wo5UvlOs/qts1iP8XVKT +y/uj9bcbAgMBAAECggEAXqkMZtGdZCUz4TT44IZ0eGbkZg8qamjbLG3FawLMllXs +QZYrFzEhjTfltTYG4D8MpH3DgXdsFMzSGytjYkagOK+LzV9UlOhVbAgI258LiDAA +TM0J6CGu0irg4/FdapLd/CvX+anNgaBlC1Lilv7FHQYG1WeHlPtLhRiONxa/LYtb +xpUV9RqazNAFZGSG7EPWfgj9CQOkX8pV6CJE3o+b7jLZfDFF/CVQVei556b3EAkk +REVe334UA5awosIxu/DjXSMi5KTlsRksT3LvFRzRxg9bbzGBOEXAECxEQBpKcr75 +NlsDr+6rSCEv1R6dIbURYYf1i1mHio2Z1qXDxYyqwQKBgQDk6+jo7Um35kCr3e9M +wsBQa244812CrdGdEsA3zT0U1YWs6yDnewGffQA4CcEtxRxs60Xl2qxpn1mgeCdy +tvGOCkQBKiLfuQJAoIudXsgrUTeOXKWyXdpgkK687nwtXg7lOcp1+bH3p3YsOoKQ +lLAP7N2+QybZOVxgfipWc3vnyQKBgQDZlo8WJMudfAnvrUSHjNkUjiPqs7iLwwvu +otitFZ3YKUa28TSJdHodPHFgQJdb1OHsLdsHwcz7+OEtMwAKyQ+zbuy0sFP4jnn7 +7bOOkY0G1upTCynPrXj54CrDneTT0j0ikEP9vgCLhNRK9OX7oMG01/r45BBTHIDE +D0aLLjlhwwKBgH7kAUNznEQyfjCGIYoT/ZPWKM+qnm+8N49wgFmuCyiMPr+dyaxl +831bRY7KYWkkdGAvfZwuPRmC+aRIVd1xaK3KJO3cVF5cZ9I464q8qgnQyBHCaxpW +iaCzMhiJvQ1MsMcA5KfGU46qJYfYmtzXfkwliLhY8qS/9eOKq58l/k15AoGAMWBR +KB1Bd4NmXdVb78aunFOFIwWVo2Gnm4eo095L63myamFiIq8j5u6Ia+c8ccJlYksl +oSUBd0yLDM69+7SUs4tAe+BnrcfnNpxCWt/8uMicdCvcWRxsj4enLKzv+IGFDgre +4v3y8bY61qesaOWaD4fTlBds/O9C6TruLzdWHjECgYBPyL/KonHisxwmL7HTRXzn +OtxIDysEem/a8A2YOVWv//Ge4kpS3r+lDDxUewxAzyKxkR4uuPMyaNZ7wNkV/bzZ +fxvlcGS4agztOvqzimieUDMnePSQQiz3zar4iPHmypJ/xUWCeGdZ95HFXMhAUNGu +aPxqpOZa9Q2B5HKNCY2bvA== +-----END PRIVATE KEY----- diff --git a/apps/nginx/ssl/openssl.cnf b/apps/nginx/ssl/openssl.cnf new file mode 100644 index 0000000000..052318e9fb --- /dev/null +++ b/apps/nginx/ssl/openssl.cnf @@ -0,0 +1,22 @@ +[req] +default_bits = 2048 +distinguished_name = req_distinguished_name +x509_extensions = v3_req +req_extensions = v3_req +prompt = no + +[req_distinguished_name] +C = US +ST = California +L = San Francisco +O = Onlook +OU = Eng +CN = onlook.internal +emailAddress = eng@onlook.com + +[v3_req] +subjectAltName = @alt_names + +[alt_names] +DNS.1 = onlook.internal +DNS.2 = *.onlook.internal diff --git a/apps/web/client/.env.example b/apps/web/client/.env.example index b8d0a1a86d..d2ba300cf0 100644 --- a/apps/web/client/.env.example +++ b/apps/web/client/.env.example @@ -10,7 +10,7 @@ SUPABASE_DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:54322/postgres # Anthropic - Enables AI chat. Other providers are optional below ANTHROPIC_API_KEY="" -# Codesandbox - Used to host user apps. Other providers may be supported in the future. May be optional in the future. +# Codesandbox - Used to host user apps. CSB_API_KEY="" # Fast apply model providers to reliably resolve code changes. Either will work since we will fall back. @@ -68,3 +68,8 @@ GOOGLE_PRIVATE_KEY_ID=" { + getSession: async (_, sandboxId, userId) => { return startSandbox({ sandboxId, userId, }); }, - }, + } + // codesandbox: { + // sandboxId: forkedSandbox.sandboxId, + // userId: user.id, + // initClient: true, + // keepActiveWhileConnected: false, + // getSession: async (sandboxId, userId) => { + // return startSandbox({ + // sandboxId, + // userId, + // }); + // }, + // }, }, }); diff --git a/apps/web/client/src/components/store/editor/code/index.ts b/apps/web/client/src/components/store/editor/code/index.ts index 7effc9b72f..ce630dbe6b 100644 --- a/apps/web/client/src/components/store/editor/code/index.ts +++ b/apps/web/client/src/components/store/editor/code/index.ts @@ -74,7 +74,10 @@ export class CodeManager { toast.error('Error writing requests', { description: error instanceof Error ? error.message : 'Unknown error', }); - this.editorEngine.error.addCodeApplicationError(error instanceof Error ? error.message : 'Unknown error', action); + this.editorEngine.error.addCodeApplicationError( + error instanceof Error ? error.message : 'Unknown error', + action, + ); } } @@ -137,5 +140,5 @@ export class CodeManager { return requestByFile; } - clear() { } + clear() {} } diff --git a/apps/web/client/src/components/store/editor/sandbox/session.ts b/apps/web/client/src/components/store/editor/sandbox/session.ts index 49a9bc7e46..8a9391a7d4 100644 --- a/apps/web/client/src/components/store/editor/sandbox/session.ts +++ b/apps/web/client/src/components/store/editor/sandbox/session.ts @@ -19,18 +19,29 @@ export class SessionManager { return; } this.isConnecting = true; - + try { - this.provider = await createCodeProviderClient(CodeProvider.CodeSandbox, { + this.provider = await createCodeProviderClient(CodeProvider.Coderouter, { providerOptions: { - codesandbox: { - sandboxId, - userId, - initClient: true, - getSession: async (sandboxId, userId) => { + coderouter: { + sandboxId: sandboxId, + userId: userId, + getSession: async (_, sandboxId, userId) => { return api.sandbox.start.mutate({ sandboxId, userId }); }, }, + // codesandbox: { + // sandboxId: forkedSandbox.sandboxId, + // userId: user.id, + // initClient: true, + // keepActiveWhileConnected: false, + // getSession: async (sandboxId, userId) => { + // return startSandbox({ + // sandboxId, + // userId, + // }); + // }, + // }, }, }); await this.createTerminalSessions(this.provider); diff --git a/apps/web/client/src/components/store/editor/sandbox/terminal.ts b/apps/web/client/src/components/store/editor/sandbox/terminal.ts index 83f1831b3b..f19820389a 100644 --- a/apps/web/client/src/components/store/editor/sandbox/terminal.ts +++ b/apps/web/client/src/components/store/editor/sandbox/terminal.ts @@ -1,7 +1,7 @@ 'use client'; import type { Provider, ProviderTask, ProviderTerminal } from '@onlook/code-provider'; -import { FitAddon } from '@xterm/addon-fit'; +// import { FitAddon } from '@xterm/addon-fit'; import { Terminal as XTerm } from '@xterm/xterm'; import { v4 as uuidv4 } from 'uuid'; import type { ErrorManager } from '../error'; @@ -19,7 +19,7 @@ export interface CLISession { // Task is readonly task: ProviderTask | null; xterm: XTerm; - fitAddon: FitAddon; + // fitAddon: FitAddon; } export interface TaskSession extends CLISession { @@ -37,7 +37,7 @@ export class CLISessionImpl implements CLISession { terminal: ProviderTerminal | null; task: ProviderTask | null; xterm: XTerm; - fitAddon: FitAddon; + // fitAddon: FitAddon; constructor( public readonly name: string, @@ -46,9 +46,9 @@ export class CLISessionImpl implements CLISession { private readonly errorManager: ErrorManager, ) { this.id = uuidv4(); - this.fitAddon = new FitAddon(); + // this.fitAddon = new FitAddon(); this.xterm = this.createXTerm(); - this.xterm.loadAddon(this.fitAddon); + // this.xterm.loadAddon(this.fitAddon); this.terminal = null; this.task = null; @@ -86,10 +86,14 @@ export class CLISessionImpl implements CLISession { await terminal.open(); // Set initial terminal size and environment - if (this.xterm.cols && this.xterm.rows && 'resize' in terminal && typeof terminal.resize === 'function') { + if ( + this.xterm.cols && + this.xterm.rows && + 'resize' in terminal && + typeof terminal.resize === 'function' + ) { terminal.resize(this.xterm.cols, this.xterm.rows); } - } catch (error) { console.error('Failed to initialize terminal:', error); this.terminal = null; diff --git a/apps/web/client/src/env.ts b/apps/web/client/src/env.ts index ab7895c9be..7c120e2abb 100644 --- a/apps/web/client/src/env.ts +++ b/apps/web/client/src/env.ts @@ -46,6 +46,11 @@ export const env = createEnv({ // Exa EXA_API_KEY: z.string().optional(), + + // E2B + E2B_DEFAULT_TEMPLATE_ID: z.string().optional(), + SUPAROUTA_API_KEY: z.string().optional(), + SUPAROUTA_HOST_URL: z.string().optional(), }, /** * Specify your client-side environment variables schema here. This way you can ensure the app @@ -119,6 +124,11 @@ export const env = createEnv({ // Exa EXA_API_KEY: process.env.EXA_API_KEY, + + // E2B + E2B_DEFAULT_TEMPLATE_ID: process.env.E2B_DEFAULT_TEMPLATE_ID, + SUPAROUTA_API_KEY: process.env.SUPAROUTA_API_KEY, + SUPAROUTA_HOST_URL: process.env.SUPAROUTA_HOST_URL, }, /** * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially diff --git a/apps/web/client/src/server/api/routers/project/sandbox.ts b/apps/web/client/src/server/api/routers/project/sandbox.ts index 006efbef01..1c2cc49ad6 100644 --- a/apps/web/client/src/server/api/routers/project/sandbox.ts +++ b/apps/web/client/src/server/api/routers/project/sandbox.ts @@ -5,14 +5,38 @@ import { TRPCError } from '@trpc/server'; import { v4 as uuidv4 } from 'uuid'; import { z } from 'zod'; import { createTRPCRouter, protectedProcedure } from '../../trpc'; +import { env } from '@/env'; function getProvider(sandboxId: string, userId?: string) { - return createCodeProviderClient(CodeProvider.CodeSandbox, { + return createCodeProviderClient(CodeProvider.Coderouter, { providerOptions: { - codesandbox: { - sandboxId, - userId, + coderouter: { + url: env.SUPAROUTA_HOST_URL, + sandboxId: sandboxId, + userId: userId, + getSession: async (provider, sandboxId, userId) => { + const res = await provider.createSession({ + args: { + id: shortenUuid(userId ?? uuidv4(), 20), + }, + }); + return { + jwt: res.jwt, + }; + }, }, + // codesandbox: { + // sandboxId: forkedSandbox.sandboxId, + // userId: user.id, + // initClient: true, + // keepActiveWhileConnected: false, + // getSession: async (sandboxId, userId) => { + // return startSandbox({ + // sandboxId, + // userId, + // }); + // }, + // }, }, }); } @@ -27,13 +51,17 @@ export const sandboxRouter = createTRPCRouter({ ) .mutation(async ({ input }) => { const provider = await getProvider(input.sandboxId, input.userId); - const session = await provider.createSession({ - args: { - id: shortenUuid(input.userId ?? uuidv4(), 20), - }, - }); - await provider.destroy(); - return session; + try { + const session = await provider.createSession({ + args: { + id: shortenUuid(input.userId ?? uuidv4(), 20), + }, + }); + await provider.destroy(); + return session; + } catch (error) { + throw error; + } }), hibernate: protectedProcedure .input( @@ -58,6 +86,7 @@ export const sandboxRouter = createTRPCRouter({ fork: protectedProcedure .input( z.object({ + userId: z.string().optional(), sandbox: z.object({ id: z.string(), port: z.number(), @@ -80,14 +109,18 @@ export const sandboxRouter = createTRPCRouter({ const sandbox = await provider.createProject({ source: 'template', id: input.sandbox.id, - + userId: input.userId, + templateId: env.E2B_DEFAULT_TEMPLATE_ID, // Metadata title: input.config?.title, tags: input.config?.tags, }); - await provider.destroy(); - const previewUrl = getSandboxPreviewUrl(sandbox.id, input.sandbox.port); + const previewUrl = await provider + .getProjectUrl({ args: {} }) + .then((res) => res.url); + + await provider.destroy(); return { sandboxId: sandbox.id, @@ -98,7 +131,9 @@ export const sandboxRouter = createTRPCRouter({ console.error(`Sandbox creation attempt ${attempt} failed:`, lastError); if (attempt < maxRetries) { - await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000)); + await new Promise((resolve) => + setTimeout(resolve, Math.pow(2, attempt) * 1000), + ); } } } diff --git a/apps/web/client/src/server/api/routers/publish/helpers/fork.ts b/apps/web/client/src/server/api/routers/publish/helpers/fork.ts index 8a756ee486..a5e618a527 100644 --- a/apps/web/client/src/server/api/routers/publish/helpers/fork.ts +++ b/apps/web/client/src/server/api/routers/publish/helpers/fork.ts @@ -1,21 +1,27 @@ +import { env } from '@/env'; import { CodeProvider, createCodeProviderClient, type Provider } from '@onlook/code-provider'; +import { v4 as uuidv4 } from 'uuid'; export async function forkBuildSandbox( sandboxId: string, userId: string, deploymentId: string, ): Promise<{ provider: Provider; sandboxId: string }> { - // not a big fan of this pattern... may need to refactor `createProject` into a static method - // though, that entirely depends on other provider implementations - const provider = await createCodeProviderClient(CodeProvider.CodeSandbox, { + const newSandboxId = uuidv4(); + const provider = await createCodeProviderClient(CodeProvider.Coderouter, { providerOptions: { - codesandbox: {}, + coderouter: { + url: env.SUPAROUTA_HOST_URL, + sandboxId: newSandboxId, + userId: userId, + }, }, }); const project = await provider.createProject({ source: 'template', - id: sandboxId, + id: newSandboxId, + userId, title: 'Deployment Fork of ' + sandboxId, description: 'Forked sandbox for deployment', tags: ['deployment', 'preview', userId, deploymentId], @@ -23,18 +29,8 @@ export async function forkBuildSandbox( await provider.destroy(); - const forkedProvider = await createCodeProviderClient(CodeProvider.CodeSandbox, { - providerOptions: { - codesandbox: { - sandboxId: project.id, - userId, - initClient: true, - }, - }, - }); - return { - provider: forkedProvider, + provider, sandboxId: project.id, }; } diff --git a/apps/web/template/.gitignore b/apps/web/template/.gitignore index afc1e37a65..8e9f9d531e 100644 --- a/apps/web/template/.gitignore +++ b/apps/web/template/.gitignore @@ -28,6 +28,7 @@ yarn-error.log* # local env files .env*.local +e2b.toml # vercel .vercel diff --git a/apps/web/template/README.md b/apps/web/template/README.md index 839f8b39e2..670df46e25 100644 --- a/apps/web/template/README.md +++ b/apps/web/template/README.md @@ -23,3 +23,8 @@ bun dev ``` Open [http://localhost:3000](http://localhost:3000) in Onlook to see the result. + +## Running the template on E2B + +1. Follow the instructions here: https://e2b.dev/docs/sandbox-template#how-to-create-custom-sandbox +2. On the build step, run the following: `e2b template build -c "(cd code && bun dev)"` diff --git a/apps/web/template/e2b.Dockerfile b/apps/web/template/e2b.Dockerfile new file mode 100644 index 0000000000..79ade4820a --- /dev/null +++ b/apps/web/template/e2b.Dockerfile @@ -0,0 +1,22 @@ +# You can use most Debian-based base images +FROM ubuntu:22.04 + +# Install dependencies and customize sandbox +WORKDIR /code + +ENV NODE_VERSION=24 +ENV NVM_DIR=/root/.nvm + +RUN apt-get update && apt-get install -y curl unzip +RUN curl -fsSL https://bun.com/install | bash +RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash && \ + . $NVM_DIR/nvm.sh && \ + nvm install $NODE_VERSION && \ + nvm use $NODE_VERSION + +COPY . . +RUN /root/.bun/bin/bun install + +EXPOSE 3000 + +# CMD ["/root/.bun/bin/bun", "run", "dev"] diff --git a/apps/web/template/e2b.toml.example b/apps/web/template/e2b.toml.example new file mode 100644 index 0000000000..0134132337 --- /dev/null +++ b/apps/web/template/e2b.toml.example @@ -0,0 +1,18 @@ +# Replace ######### with your template ID from E2B +# +# This is a config for E2B sandbox template. +# You can use template ID (#########) to create a sandbox: + +# Python SDK +# from e2b import Sandbox, AsyncSandbox +# sandbox = Sandbox("#########") # Sync sandbox +# sandbox = await AsyncSandbox.create("#########") # Async sandbox + +# JS SDK +# import { Sandbox } from 'e2b' +# const sandbox = await Sandbox.create('#########') + +team_id = "27b622d8-2199-4c46-9e8c-7cb1b00da325" +start_cmd = "(cd /code && bun dev)" +dockerfile = "e2b.Dockerfile" +template_id = "#########" diff --git a/bun.lock b/bun.lock index 098fecd118..897a1c1bdd 100644 --- a/bun.lock +++ b/bun.lock @@ -254,6 +254,7 @@ }, "devDependencies": { "@onlook/typescript": "*", + "@openapitools/openapi-generator-cli": "^2.22.0", }, }, "packages/constants": { @@ -735,6 +736,8 @@ "@babel/types": ["@babel/types@7.27.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q=="], + "@borewit/text-codec": ["@borewit/text-codec@0.1.1", "", {}, "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA=="], + "@codemirror/autocomplete": ["@codemirror/autocomplete@6.18.6", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" } }, "sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg=="], "@codemirror/commands": ["@codemirror/commands@6.8.1", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.4.0", "@codemirror/view": "^6.27.0", "@lezer/common": "^1.1.0" } }, "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw=="], @@ -1015,6 +1018,8 @@ "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="], + "@inquirer/external-editor": ["@inquirer/external-editor@1.0.1", "", { "dependencies": { "chardet": "^2.1.0", "iconv-lite": "^0.6.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-Oau4yL24d2B5IL4ma4UpbQigkVhzPDXLoqy1ggK4gnHg/stmkffJE4oOXHXF3uz0UEpywG68KcyXsyYpA1Re/Q=="], + "@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="], "@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="], @@ -1099,6 +1104,8 @@ "@libsql/win32-x64-msvc": ["@libsql/win32-x64-msvc@0.4.7", "", { "os": "win32", "cpu": "x64" }, "sha512-7pJzOWzPm6oJUxml+PCDRzYQ4A1hTMHAciTAHfFK4fkbDZX33nWPVG7Y3vqdKtslcwAzwmrNDc6sXy2nwWnbiw=="], + "@lukeed/csprng": ["@lukeed/csprng@1.1.0", "", {}, "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA=="], + "@marijn/find-cluster-break": ["@marijn/find-cluster-break@1.0.2", "", {}, "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="], "@mastra/core": ["@mastra/core@0.13.1", "", { "dependencies": { "@ai-sdk/provider": "^1.1.3", "@ai-sdk/provider-utils": "^2.2.8", "@ai-sdk/ui-utils": "^1.2.11", "@mastra/schema-compat": "0.10.6", "@opentelemetry/api": "^1.9.0", "@opentelemetry/auto-instrumentations-node": "^0.62.0", "@opentelemetry/core": "^2.0.1", "@opentelemetry/exporter-trace-otlp-grpc": "^0.203.0", "@opentelemetry/exporter-trace-otlp-http": "^0.203.0", "@opentelemetry/otlp-exporter-base": "^0.203.0", "@opentelemetry/otlp-transformer": "^0.203.0", "@opentelemetry/resources": "^2.0.1", "@opentelemetry/sdk-metrics": "^2.0.1", "@opentelemetry/sdk-node": "^0.203.0", "@opentelemetry/sdk-trace-base": "^2.0.1", "@opentelemetry/sdk-trace-node": "^2.0.1", "@opentelemetry/semantic-conventions": "^1.36.0", "@sindresorhus/slugify": "^2.2.1", "ai": "^4.3.16", "date-fns": "^3.6.0", "dotenv": "^16.6.1", "hono": "^4.8.12", "hono-openapi": "^0.4.8", "json-schema": "^0.4.0", "json-schema-to-zod": "^2.6.1", "pino": "^9.7.0", "pino-pretty": "^13.0.0", "radash": "^12.1.1", "sift": "^17.1.3", "xstate": "^5.20.1", "zod-to-json-schema": "^3.24.5" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-pifAaarI/mdPl/F62kD6YovTrK14seBvxVeB5qjxGXdhMCJGXcSDPMus8Ni9leP/VXrB9+EQu6+oo/lGf0JBvg=="], @@ -1117,6 +1124,12 @@ "@neon-rs/load": ["@neon-rs/load@0.0.4", "", {}, "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw=="], + "@nestjs/axios": ["@nestjs/axios@4.0.1", "", { "peerDependencies": { "@nestjs/common": "^10.0.0 || ^11.0.0", "axios": "^1.3.1", "rxjs": "^7.0.0" } }, "sha512-68pFJgu+/AZbWkGu65Z3r55bTsCPlgyKaV4BSG8yUAD72q1PPuyVRgUwFv6BxdnibTUHlyxm06FmYWNC+bjN7A=="], + + "@nestjs/common": ["@nestjs/common@11.1.6", "", { "dependencies": { "file-type": "21.0.0", "iterare": "1.2.1", "load-esm": "1.0.2", "tslib": "2.8.1", "uid": "2.0.2" }, "peerDependencies": { "class-transformer": ">=0.4.1", "class-validator": ">=0.13.2", "reflect-metadata": "^0.1.12 || ^0.2.0", "rxjs": "^7.1.0" }, "optionalPeers": ["class-transformer", "class-validator"] }, "sha512-krKwLLcFmeuKDqngG2N/RuZHCs2ycsKcxWIDgcm7i1lf3sQ0iG03ci+DsP/r3FcT/eJDFsIHnKtNta2LIi7PzQ=="], + + "@nestjs/core": ["@nestjs/core@11.1.6", "", { "dependencies": { "@nuxt/opencollective": "0.4.1", "fast-safe-stringify": "2.1.1", "iterare": "1.2.1", "path-to-regexp": "8.2.0", "tslib": "2.8.1", "uid": "2.0.2" }, "peerDependencies": { "@nestjs/common": "^11.0.0", "@nestjs/microservices": "^11.0.0", "@nestjs/platform-express": "^11.0.0", "@nestjs/websockets": "^11.0.0", "reflect-metadata": "^0.1.12 || ^0.2.0", "rxjs": "^7.1.0" }, "optionalPeers": ["@nestjs/microservices", "@nestjs/platform-express", "@nestjs/websockets"] }, "sha512-siWX7UDgErisW18VTeJA+x+/tpNZrJewjTBsRPF3JVxuWRuAB1kRoiJcxHgln8Lb5UY9NdvklITR84DUEXD0Cg=="], + "@next/env": ["@next/env@15.3.1", "", {}, "sha512-cwK27QdzrMblHSn9DZRV+DQscHXRuJv6MydlJRpFSqJWZrTYMLzKDeyueJNN9MGd8NNiUKzDQADAf+dMLXX7YQ=="], "@next/eslint-plugin-next": ["@next/eslint-plugin-next@15.3.1", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-oEs4dsfM6iyER3jTzMm4kDSbrQJq8wZw5fmT6fg2V3SMo+kgG+cShzLfEV20senZzv8VF+puNLheiGPlBGsv2A=="], @@ -1147,6 +1160,10 @@ "@nolyfill/is-core-module": ["@nolyfill/is-core-module@1.0.39", "", {}, "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA=="], + "@nuxt/opencollective": ["@nuxt/opencollective@0.4.1", "", { "dependencies": { "consola": "^3.2.3" }, "bin": { "opencollective": "bin/opencollective.js" } }, "sha512-GXD3wy50qYbxCJ652bDrDzgMr3NFEkIS374+IgFQKkCvk9yiYcLvX2XDYr7UyQxf4wK0e+yqDYRubZ0DtOxnmQ=="], + + "@nuxtjs/opencollective": ["@nuxtjs/opencollective@0.3.2", "", { "dependencies": { "chalk": "^4.1.0", "consola": "^2.15.0", "node-fetch": "^2.6.1" }, "bin": { "opencollective": "bin/opencollective.js" } }, "sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA=="], + "@octokit/app": ["@octokit/app@16.0.1", "", { "dependencies": { "@octokit/auth-app": "^8.0.1", "@octokit/auth-unauthenticated": "^7.0.1", "@octokit/core": "^7.0.2", "@octokit/oauth-app": "^8.0.1", "@octokit/plugin-paginate-rest": "^13.0.0", "@octokit/types": "^14.0.0", "@octokit/webhooks": "^14.0.0" } }, "sha512-kgTeTsWmpUX+s3Fs4EK4w1K+jWCDB6ClxLSWUWTyhlw7+L3jHtuXDR4QtABu2GsmCMdk67xRhruiXotS3ay3Yw=="], "@octokit/auth-app": ["@octokit/auth-app@8.0.1", "", { "dependencies": { "@octokit/auth-oauth-app": "^9.0.1", "@octokit/auth-oauth-user": "^6.0.0", "@octokit/request": "^10.0.2", "@octokit/request-error": "^7.0.0", "@octokit/types": "^14.0.0", "toad-cache": "^3.7.0", "universal-github-app-jwt": "^2.2.0", "universal-user-agent": "^7.0.0" } }, "sha512-P2J5pB3pjiGwtJX4WqJVYCtNkcZ+j5T2Wm14aJAEIC3WJOrv12jvBley3G1U/XI8q9o1A7QMG54LiFED2BiFlg=="], @@ -1249,6 +1266,8 @@ "@onlook/web-template": ["@onlook/web-template@workspace:apps/web/template"], + "@openapitools/openapi-generator-cli": ["@openapitools/openapi-generator-cli@2.22.0", "", { "dependencies": { "@nestjs/axios": "4.0.1", "@nestjs/common": "11.1.6", "@nestjs/core": "11.1.6", "@nuxtjs/opencollective": "0.3.2", "axios": "1.11.0", "chalk": "4.1.2", "commander": "8.3.0", "compare-versions": "4.1.4", "concurrently": "9.2.0", "console.table": "0.10.0", "fs-extra": "11.3.1", "glob": "11.0.3", "inquirer": "8.2.7", "proxy-agent": "6.5.0", "reflect-metadata": "0.2.2", "rxjs": "7.8.2", "tslib": "2.8.1" }, "bin": { "openapi-generator-cli": "main.js" } }, "sha512-HdjSiKsXpbnXBcSCnft494fv5pFZxPKFAV1eR+yMjo3bt1ONLb7OGy1D/5SrbjRfy9b82JcYUJ3gssh49suWKg=="], + "@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@0.7.5", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8" }, "peerDependencies": { "ai": "^4.3.17", "zod": "^3.25.34" } }, "sha512-zm8vBhQ+GhxN03Y41xviB0nDa20uN77QnMXsIwDeJPqsul8+KycrYFxY4ulXpumeKxjKyOhfyA7a7CJpcYq2ng=="], "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], @@ -1747,6 +1766,12 @@ "@tanstack/react-query": ["@tanstack/react-query@5.80.7", "", { "dependencies": { "@tanstack/query-core": "5.80.7" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-u2F0VK6+anItoEvB3+rfvTO9GEh2vb00Je05OwlUe/A0lkJBgW1HckiY3f9YZa+jx6IOe4dHPh10dyp9aY3iRQ=="], + "@tokenizer/inflate": ["@tokenizer/inflate@0.2.7", "", { "dependencies": { "debug": "^4.4.0", "fflate": "^0.8.2", "token-types": "^6.0.0" } }, "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg=="], + + "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], + + "@tootallnate/quickjs-emscripten": ["@tootallnate/quickjs-emscripten@0.23.0", "", {}, "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA=="], + "@trpc/client": ["@trpc/client@11.4.1", "", { "peerDependencies": { "@trpc/server": "11.4.1", "typescript": ">=5.7.2" } }, "sha512-h28HKqxOBu35Q3f7h2chOjkQnwmIFdZDqG6NxovPaxEGcUmQWdo63mthlPSiMThXpy9J1AUA8q4uZZ4a5d1JVA=="], "@trpc/react-query": ["@trpc/react-query@11.4.1", "", { "peerDependencies": { "@tanstack/react-query": "^5.80.3", "@trpc/client": "11.4.1", "@trpc/server": "11.4.1", "react": ">=18.2.0", "react-dom": ">=18.2.0", "typescript": ">=5.7.2" } }, "sha512-DpwmD9EkFX+dcpY0wH0T8MsNLERs8r1EJ4yyZy7o3v/g3DgcrfxGOeM3CNChEgSX2CcjBJsmhpQPz4spoy9wGA=="], @@ -2017,6 +2042,8 @@ "assert-options": ["assert-options@0.8.3", "", {}, "sha512-s6v4HnA+vYSGO4eZX+F+I3gvF74wPk+m6Z1Q3w1Dsg4Pnv/R24vhKAasoMVZGvDpOOfTg1Qz4ptZnEbuy95XsQ=="], + "ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="], + "ast-types-flow": ["ast-types-flow@0.0.8", "", {}, "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ=="], "astring": ["astring@1.9.0", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="], @@ -2079,6 +2106,8 @@ "base64id": ["base64id@2.0.0", "", {}, "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="], + "basic-ftp": ["basic-ftp@5.0.5", "", {}, "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg=="], + "before-after-hook": ["before-after-hook@4.0.0", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="], "better-opn": ["better-opn@3.0.2", "", { "dependencies": { "open": "^8.0.4" } }, "sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ=="], @@ -2169,6 +2198,8 @@ "character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="], + "chardet": ["chardet@2.1.0", "", {}, "sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA=="], + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], @@ -2191,6 +2222,8 @@ "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], + "cli-width": ["cli-width@3.0.0", "", {}, "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw=="], + "client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="], "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], @@ -2227,6 +2260,8 @@ "commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], + "compare-versions": ["compare-versions@4.1.4", "", {}, "sha512-FemMreK9xNyL8gQevsdRMrvO4lFCkQP7qbuktn1q8ndcNk1+0mz7lgE7b/sNvbhVgY4w6tMN1FDp6aADjqw2rw=="], + "compressible": ["compressible@2.0.18", "", { "dependencies": { "mime-db": ">= 1.43.0 < 2" } }, "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg=="], "compression": ["compression@1.8.0", "", { "dependencies": { "bytes": "3.1.2", "compressible": "~2.0.18", "debug": "2.6.9", "negotiator": "~0.6.4", "on-headers": "~1.0.2", "safe-buffer": "5.2.1", "vary": "~1.1.2" } }, "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA=="], @@ -2239,6 +2274,10 @@ "connect": ["connect@3.7.0", "", { "dependencies": { "debug": "2.6.9", "finalhandler": "1.1.2", "parseurl": "~1.3.3", "utils-merge": "1.0.1" } }, "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ=="], + "consola": ["consola@2.15.3", "", {}, "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw=="], + + "console.table": ["console.table@0.10.0", "", { "dependencies": { "easy-table": "1.1.0" } }, "sha512-dPyZofqggxuvSf7WXvNjuRfnsOk1YazkVP8FdxH4tcH2c37wc79/Yl6Bhr7Lsu00KMgy2ql/qCMuNu8xctZM8g=="], + "constant-case": ["constant-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3", "upper-case": "^2.0.2" } }, "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ=="], "convert-source-map": ["convert-source-map@1.9.0", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="], @@ -2343,6 +2382,8 @@ "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="], + "degenerator": ["degenerator@5.0.1", "", { "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", "esprima": "^4.0.1" } }, "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="], + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], @@ -2399,6 +2440,8 @@ "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + "easy-table": ["easy-table@1.1.0", "", { "optionalDependencies": { "wcwidth": ">=1.0.1" } }, "sha512-oq33hWOSSnl2Hoh00tZWaIPi1ievrD9aFG82/IgjlycAnW9hHx5PkJiXpxPsgEE+H7BsbVQXFVFST8TEXS6/pA=="], + "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], @@ -2469,6 +2512,8 @@ "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + "escodegen": ["escodegen@2.1.0", "", { "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2" }, "optionalDependencies": { "source-map": "~0.6.1" }, "bin": { "esgenerate": "bin/esgenerate.js", "escodegen": "bin/escodegen.js" } }, "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w=="], + "eslint": ["eslint@8.57.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "8.57.1", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" } }, "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA=="], "eslint-config-next": ["eslint-config-next@15.3.1", "", { "dependencies": { "@next/eslint-plugin-next": "15.3.1", "@rushstack/eslint-patch": "^1.10.3", "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", "eslint-plugin-react-hooks": "^5.0.0" }, "peerDependencies": { "eslint": "^7.23.0 || ^8.0.0 || ^9.0.0", "typescript": ">=3.3.1" }, "optionalPeers": ["typescript"] }, "sha512-GnmyVd9TE/Ihe3RrvcafFhXErErtr2jS0JDeCSp3vWvy86AXwHsRBt0E3MqP/m8ACS1ivcsi5uaqjbhsG18qKw=="], @@ -2605,8 +2650,12 @@ "fg": ["fg@0.0.3", "", { "dependencies": { "bluebird": "^2.3.11", "pg": "^3.6.3", "sql-bricks-postgres": "^0.3.0" } }, "sha512-I/SEHQDjxA5VW2mKmgNPneNoFz7kJDl4oPQc2StQUB6g8iSwTRY5up2T2LopKfpXFqP2xzaDhf6xbRVkH47MPA=="], + "figures": ["figures@3.2.0", "", { "dependencies": { "escape-string-regexp": "^1.0.5" } }, "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg=="], + "file-entry-cache": ["file-entry-cache@6.0.1", "", { "dependencies": { "flat-cache": "^3.0.4" } }, "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg=="], + "file-type": ["file-type@21.0.0", "", { "dependencies": { "@tokenizer/inflate": "^0.2.7", "strtok3": "^10.2.2", "token-types": "^6.0.0", "uint8array-extras": "^1.4.0" } }, "sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg=="], + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], "filter-obj": ["filter-obj@1.1.0", "", {}, "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ=="], @@ -2655,6 +2704,8 @@ "fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="], + "fs-extra": ["fs-extra@11.3.1", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g=="], + "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], @@ -2697,6 +2748,8 @@ "get-tsconfig": ["get-tsconfig@4.10.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ=="], + "get-uri": ["get-uri@6.0.5", "", { "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4" } }, "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg=="], + "getenv": ["getenv@2.0.0", "", {}, "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ=="], "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], @@ -2777,12 +2830,16 @@ "http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="], + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], "humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="], "husky": ["husky@9.1.7", "", { "bin": { "husky": "bin.js" } }, "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA=="], + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], @@ -2807,6 +2864,8 @@ "input-otp": ["input-otp@1.4.2", "", { "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA=="], + "inquirer": ["inquirer@8.2.7", "", { "dependencies": { "@inquirer/external-editor": "^1.0.0", "ansi-escapes": "^4.2.1", "chalk": "^4.1.1", "cli-cursor": "^3.1.0", "cli-width": "^3.0.0", "figures": "^3.0.0", "lodash": "^4.17.21", "mute-stream": "0.0.8", "ora": "^5.4.1", "run-async": "^2.4.0", "rxjs": "^7.5.5", "string-width": "^4.1.0", "strip-ansi": "^6.0.0", "through": "^2.3.6", "wrap-ansi": "^6.0.1" } }, "sha512-UjOaSel/iddGZJ5xP/Eixh6dY1XghiBw4XK13rCCIJcJfyhhoul/7KhLLUGtebEj6GDYM6Vnx/mVsjx2L/mFIA=="], + "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], @@ -2815,6 +2874,8 @@ "invariant": ["invariant@2.2.4", "", { "dependencies": { "loose-envify": "^1.0.0" } }, "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA=="], + "ip-address": ["ip-address@10.0.1", "", {}, "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA=="], + "ipaddr.js": ["ipaddr.js@2.2.0", "", {}, "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA=="], "is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], @@ -2917,6 +2978,8 @@ "istanbul-lib-instrument": ["istanbul-lib-instrument@5.2.1", "", { "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-coverage": "^3.2.0", "semver": "^6.3.0" } }, "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg=="], + "iterare": ["iterare@1.2.1", "", {}, "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q=="], + "iterator.prototype": ["iterator.prototype@1.1.5", "", { "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "get-proto": "^1.0.0", "has-symbols": "^1.1.0", "set-function-name": "^2.0.2" } }, "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g=="], "jackspeak": ["jackspeak@4.1.1", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" } }, "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ=="], @@ -2985,6 +3048,8 @@ "jsondiffpatch": ["jsondiffpatch@0.6.0", "", { "dependencies": { "@types/diff-match-patch": "^1.0.36", "chalk": "^5.3.0", "diff-match-patch": "^1.0.5" }, "bin": { "jsondiffpatch": "bin/jsondiffpatch.js" } }, "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ=="], + "jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], + "jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="], "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="], @@ -3041,6 +3106,8 @@ "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + "load-esm": ["load-esm@1.0.2", "", {}, "sha512-nVAvWk/jeyrWyXEAs84mpQCYccxRqgKY4OznLuJhJCa0XsPSfdOIr2zvBZEj3IHEHbX97jjscKRRV539bW0Gpw=="], + "localforage": ["localforage@1.10.0", "", { "dependencies": { "lie": "3.1.1" } }, "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg=="], "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], @@ -3271,6 +3338,8 @@ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "mute-stream": ["mute-stream@0.0.8", "", {}, "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="], + "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], "nan": ["nan@1.3.0", "", {}, "sha512-2xPpifjI6OzUZtboDKhRlk9u98eV395Twdp0i0GnwP9PLGpphm4R7Q0wIZXmgmu31ukqUJCg1uXlD44fph/sKg=="], @@ -3285,6 +3354,8 @@ "nested-error-stacks": ["nested-error-stacks@2.0.1", "", {}, "sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A=="], + "netmask": ["netmask@2.0.2", "", {}, "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg=="], + "next": ["next@15.3.1", "", { "dependencies": { "@next/env": "15.3.1", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.3.1", "@next/swc-darwin-x64": "15.3.1", "@next/swc-linux-arm64-gnu": "15.3.1", "@next/swc-linux-arm64-musl": "15.3.1", "@next/swc-linux-x64-gnu": "15.3.1", "@next/swc-linux-x64-musl": "15.3.1", "@next/swc-win32-arm64-msvc": "15.3.1", "@next/swc-win32-x64-msvc": "15.3.1", "sharp": "^0.34.1" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-8+dDV0xNLOgHlyBxP1GwHGVaNXsmp+2NhZEYrXr24GWLHtt27YrBPbPuHvzlhi7kZNYjeJNR93IF5zfFu5UL0g=="], "next-intl": ["next-intl@4.1.0", "", { "dependencies": { "@formatjs/intl-localematcher": "^0.5.4", "negotiator": "^1.0.0", "use-intl": "^4.1.0" }, "peerDependencies": { "next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0", "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-JNJRjc7sdnfUxhZmGcvzDszZ60tQKrygV/VLsgzXhnJDxQPn1cN2rVpc53adA1SvBJwPK2O6Sc6b4gYSILjCzw=="], @@ -3377,6 +3448,10 @@ "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="], + "pac-proxy-agent": ["pac-proxy-agent@7.2.0", "", { "dependencies": { "@tootallnate/quickjs-emscripten": "^0.23.0", "agent-base": "^7.1.2", "debug": "^4.3.4", "get-uri": "^6.0.1", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.6", "pac-resolver": "^7.0.1", "socks-proxy-agent": "^8.0.5" } }, "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA=="], + + "pac-resolver": ["pac-resolver@7.0.1", "", { "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" } }, "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg=="], + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], "packet-reader": ["packet-reader@0.2.0", "", {}, "sha512-3GnoJ2cKeSgikX3llywfuefjtkokRfzySXO2H3bDwVguclnpkvjYcBsQNfCcUpwUKRK+4IhhCD18/rLMVOH+aQ=="], @@ -3415,6 +3490,8 @@ "path-scurry": ["path-scurry@2.0.0", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg=="], + "path-to-regexp": ["path-to-regexp@8.2.0", "", {}, "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ=="], + "path-type": ["path-type@6.0.0", "", {}, "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ=="], "peberminta": ["peberminta@0.9.0", "", {}, "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ=="], @@ -3543,6 +3620,8 @@ "protobufjs": ["protobufjs@7.5.3", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw=="], + "proxy-agent": ["proxy-agent@6.5.0", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.1", "https-proxy-agent": "^7.0.6", "lru-cache": "^7.14.1", "pac-proxy-agent": "^7.1.0", "proxy-from-env": "^1.1.0", "socks-proxy-agent": "^8.0.5" } }, "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A=="], + "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], "psl": ["psl@1.15.0", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w=="], @@ -3665,6 +3744,8 @@ "redux": ["redux@5.0.1", "", {}, "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="], + "reflect-metadata": ["reflect-metadata@0.2.2", "", {}, "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="], + "reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="], "regenerate": ["regenerate@1.4.2", "", {}, "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A=="], @@ -3735,6 +3816,8 @@ "rope-sequence": ["rope-sequence@1.3.4", "", {}, "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ=="], + "run-async": ["run-async@2.4.1", "", {}, "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ=="], + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], "rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="], @@ -3751,6 +3834,8 @@ "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="], + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + "sax": ["sax@1.4.1", "", {}, "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="], "scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="], @@ -3825,6 +3910,8 @@ "slugify": ["slugify@1.6.6", "", {}, "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw=="], + "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], + "snake-case": ["snake-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg=="], "socket.io": ["socket.io@4.8.1", "", { "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.3.2", "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" } }, "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg=="], @@ -3833,6 +3920,10 @@ "socket.io-parser": ["socket.io-parser@4.2.4", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" } }, "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew=="], + "socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="], + + "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], + "sonic-boom": ["sonic-boom@4.2.0", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww=="], "sonner": ["sonner@2.0.5", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-YwbHQO6cSso3HBXlbCkgrgzDNIhws14r4MO87Ofy+cV2X7ES4pOoAK3+veSmVTvqNx1BWUxlhPmZzP00Crk2aQ=="], @@ -3881,7 +3972,7 @@ "strict-uri-encode": ["strict-uri-encode@2.0.0", "", {}, "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ=="], - "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -3911,6 +4002,8 @@ "stripe": ["stripe@18.2.1", "", { "dependencies": { "qs": "^6.11.0" }, "peerDependencies": { "@types/node": ">=12.x.x" }, "optionalPeers": ["@types/node"] }, "sha512-GwB1B7WSwEBzW4dilgyJruUYhbGMscrwuyHsPUmSRKrGHZ5poSh2oU9XKdii5BFVJzXHn35geRvGJ6R8bYcp8w=="], + "strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="], + "structured-headers": ["structured-headers@0.4.1", "", {}, "sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg=="], "style-mod": ["style-mod@4.1.2", "", {}, "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="], @@ -3989,6 +4082,8 @@ "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], + "token-types": ["token-types@6.1.1", "", { "dependencies": { "@borewit/text-codec": "^0.1.0", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ=="], + "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], @@ -4029,6 +4124,10 @@ "typescript-event-target": ["typescript-event-target@1.1.1", "", {}, "sha512-dFSOFBKV6uwaloBCCUhxlD3Pr/P1a/tJdcmPrTXCHlEFD3faj0mztjcGn6VBAhQ0/Bdy8K3VWrrqwbt/ffsYsg=="], + "uid": ["uid@2.0.2", "", { "dependencies": { "@lukeed/csprng": "^1.0.0" } }, "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g=="], + + "uint8array-extras": ["uint8array-extras@1.4.1", "", {}, "sha512-+NWHrac9dvilNgme+gP4YrBSumsaMZP0fNBtXXFIf33RLLKEcBUKaQZ7ULUbS0sBfcjxIZ4V96OTRkCbM7hxpw=="], + "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="], "uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="], @@ -4069,6 +4168,8 @@ "universal-user-agent": ["universal-user-agent@7.0.3", "", {}, "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A=="], + "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], "unrs-resolver": ["unrs-resolver@1.9.0", "", { "dependencies": { "napi-postinstall": "^0.2.2" }, "optionalDependencies": { "@unrs/resolver-binding-android-arm-eabi": "1.9.0", "@unrs/resolver-binding-android-arm64": "1.9.0", "@unrs/resolver-binding-darwin-arm64": "1.9.0", "@unrs/resolver-binding-darwin-x64": "1.9.0", "@unrs/resolver-binding-freebsd-x64": "1.9.0", "@unrs/resolver-binding-linux-arm-gnueabihf": "1.9.0", "@unrs/resolver-binding-linux-arm-musleabihf": "1.9.0", "@unrs/resolver-binding-linux-arm64-gnu": "1.9.0", "@unrs/resolver-binding-linux-arm64-musl": "1.9.0", "@unrs/resolver-binding-linux-ppc64-gnu": "1.9.0", "@unrs/resolver-binding-linux-riscv64-gnu": "1.9.0", "@unrs/resolver-binding-linux-riscv64-musl": "1.9.0", "@unrs/resolver-binding-linux-s390x-gnu": "1.9.0", "@unrs/resolver-binding-linux-x64-gnu": "1.9.0", "@unrs/resolver-binding-linux-x64-musl": "1.9.0", "@unrs/resolver-binding-wasm32-wasi": "1.9.0", "@unrs/resolver-binding-win32-arm64-msvc": "1.9.0", "@unrs/resolver-binding-win32-ia32-msvc": "1.9.0", "@unrs/resolver-binding-win32-x64-msvc": "1.9.0" } }, "sha512-wqaRu4UnzBD2ABTC1kLfBjAqIDZ5YUTr/MLGa7By47JV1bJDSW7jq/ZSLigB7enLe7ubNaJhtnBXgrc/50cEhg=="], @@ -4157,7 +4258,7 @@ "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], - "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], @@ -4259,6 +4360,8 @@ "@expo/cli/resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], + "@expo/cli/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "@expo/config/@babel/code-frame": ["@babel/code-frame@7.10.4", "", { "dependencies": { "@babel/highlight": "^7.10.4" } }, "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg=="], "@expo/config/getenv": ["getenv@1.0.0", "", {}, "sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg=="], @@ -4357,6 +4460,8 @@ "@next/eslint-plugin-next/fast-glob": ["fast-glob@3.3.1", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg=="], + "@nuxt/opencollective/consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], + "@onlook/scripts/@types/node": ["@types/node@20.19.0", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-hfrc+1tud1xcdVTABC2JiomZJEklMcXYNTVtZLAeqTVWD+qL5jkHKT+1lOtqDdGxt+mB53DTtiz673vfjU8D1Q=="], "@onlook/ui/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], @@ -4395,6 +4500,12 @@ "@onlook/web-template/tailwindcss": ["tailwindcss@3.4.17", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.6", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og=="], + "@openapitools/openapi-generator-cli/axios": ["axios@1.11.0", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA=="], + + "@openapitools/openapi-generator-cli/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + + "@openapitools/openapi-generator-cli/concurrently": ["concurrently@9.2.0", "", { "dependencies": { "chalk": "^4.1.2", "lodash": "^4.17.21", "rxjs": "^7.8.1", "shell-quote": "^1.8.1", "supports-color": "^8.1.1", "tree-kill": "^1.2.2", "yargs": "^17.7.2" }, "bin": { "concurrently": "dist/bin/concurrently.js", "conc": "dist/bin/concurrently.js" } }, "sha512-IsB/fiXTupmagMW4MNp2lx2cdSN2FfZq78vF90LBB+zZHArbIQZjQtzXCiXnvTxCZSvXanTqFLWBjw2UkLx1SQ=="], + "@opentelemetry/instrumentation-aws-lambda/@types/aws-lambda": ["@types/aws-lambda@8.10.150", "", {}, "sha512-AX+AbjH/rH5ezX1fbK8onC/a+HyQHo7QGmvoxAE42n22OsciAxvZoZNEr22tbXs8WfP1nIsBjKDpgPm3HjOZbA=="], "@opentelemetry/instrumentation-pg/@types/pg": ["@types/pg@8.15.4", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-I6UNVBAoYbvuWkkU3oosC8yxqH21f4/Jc4DK71JLG3dT2mdlGe1z+ep/LQGXaKaOgcvUrsQoPRqfgtMcvZiJhg=="], @@ -4441,6 +4552,8 @@ "@tailwindcss/typography/postcss-selector-parser": ["postcss-selector-parser@6.0.10", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w=="], + "@tokenizer/inflate/fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], + "@types/pg/pg-protocol": ["pg-protocol@1.10.0", "", {}, "sha512-IpdytjudNuLv8nhlHs/UrVBhU0e78J0oIS/0AVdTbWxSOkFUVdsHC/NrorO6nXsQNDTT1kzDSOMJubBQviX18Q=="], "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], @@ -4477,10 +4590,10 @@ "chromium-edge-launcher/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], - "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "compression/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "compression/negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="], @@ -4505,6 +4618,8 @@ "error-ex/is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], + "escodegen/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "eslint/@eslint/eslintrc": ["@eslint/eslintrc@2.1.4", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ=="], "eslint/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], @@ -4555,6 +4670,8 @@ "fg/pg": ["pg@3.6.4", "", { "dependencies": { "bindings": "1.2.1", "buffer-writer": "1.0.0", "generic-pool": "2.1.1", "js-string-escape": "1.0.1", "nan": "1.3.0", "packet-reader": "0.2.0", "pg-connection-string": "0.1.3", "pg-types": "1.6.0", "pgpass": "0.0.3" } }, "sha512-SG/KeA0Tg81h7oOmSEsEX7e/i5OvBQO+/Hp6T/qyAyK7Zl3UcvAC+j6ZkL4LIR6Sl5IEWeYdVw8wODD+Tgt9HA=="], + "figures/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="], + "finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "finalhandler/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="], @@ -4579,6 +4696,8 @@ "gaxios/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], + "get-uri/data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="], + "glob/minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="], "globals/type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="], @@ -4589,6 +4708,8 @@ "hosted-git-info/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "inquirer/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "istanbul-lib-instrument/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "jest-environment-node/@jest/types": ["@jest/types@29.6.3", "", { "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", "@types/yargs": "^17.0.8", "chalk": "^4.0.0" } }, "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw=="], @@ -4685,6 +4806,8 @@ "prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], + "proxy-agent/lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], + "rc/strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], "react-codemirror-merge/@uiw/react-codemirror": ["@uiw/react-codemirror@4.23.10", "", { "dependencies": { "@babel/runtime": "^7.18.6", "@codemirror/commands": "^6.1.0", "@codemirror/state": "^6.1.1", "@codemirror/theme-one-dark": "^6.0.0", "@uiw/codemirror-extensions-basic-setup": "4.23.10", "codemirror": "^6.0.0" }, "peerDependencies": { "@codemirror/view": ">=6.0.0", "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-AbN4eVHOL4ckRuIXpZxkzEqL/1ChVA+BSdEnAKjIB68pLQvKsVoYbiFP8zkXkYc4+Fcgq5KbAjvYqdo4ewemKw=="], @@ -4757,7 +4880,9 @@ "stacktrace-parser/type-fest": ["type-fest@0.7.1", "", {}, "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg=="], - "string-width/emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="], + "string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -4781,20 +4906,14 @@ "whatwg-url-without-unicode/webidl-conversions": ["webidl-conversions@5.0.0", "", {}, "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA=="], - "wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - "wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "xcode/uuid": ["uuid@7.0.3", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg=="], "xml2js/xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="], - "yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - "zod-from-json-schema/zod": ["zod@3.25.64", "", {}, "sha512-hbP9FpSZf7pkS7hRVUrOjhwKJNyampPgtXKc3AN6DsWtoHsg2Sb4SQaS4Tcay380zSwd2VPo9G9180emBACp5g=="], "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], @@ -4817,6 +4936,8 @@ "@codesandbox/sdk/ora/log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="], + "@codesandbox/sdk/ora/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], @@ -4883,6 +5004,8 @@ "@expo/cli/pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], + "@expo/cli/wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "@expo/config-plugins/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], "@expo/config-plugins/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], @@ -5009,6 +5132,10 @@ "@onlook/web-template/tailwindcss/postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="], + "@openapitools/openapi-generator-cli/axios/form-data": ["form-data@4.0.4", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="], + + "@openapitools/openapi-generator-cli/concurrently/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], + "@react-native/babel-plugin-codegen/@react-native/codegen/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], "@react-native/babel-plugin-codegen/@react-native/codegen/hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="], @@ -5037,8 +5164,6 @@ "babel-preset-expo/babel-plugin-syntax-hermes-parser/hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="], - "cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "compression/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], @@ -5137,6 +5262,8 @@ "fumadocs-mdx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.8", "", { "os": "win32", "cpu": "x64" }, "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw=="], + "inquirer/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "jest-environment-node/@jest/types/@types/yargs": ["@types/yargs@17.0.33", "", { "dependencies": { "@types/yargs-parser": "*" } }, "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA=="], "jest-haste-map/@jest/types/@types/yargs": ["@types/yargs@17.0.33", "", { "dependencies": { "@types/yargs-parser": "*" } }, "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA=="], @@ -5269,6 +5396,8 @@ "react-email/ora/log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="], + "react-email/ora/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + "react-helmet-async/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], "react-native/pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], @@ -5283,6 +5412,8 @@ "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "sucrase/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], "sucrase/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], @@ -5291,18 +5422,10 @@ "use-resize-observer/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], - "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], "@babel/highlight/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="], @@ -5313,6 +5436,8 @@ "@codesandbox/sdk/ora/log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="], + "@codesandbox/sdk/ora/string-width/emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="], + "@expo/cli/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], "@expo/cli/ora/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="], @@ -5325,6 +5450,8 @@ "@expo/cli/ora/strip-ansi/ansi-regex": ["ansi-regex@4.1.1", "", {}, "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="], + "@expo/cli/wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "@expo/config-plugins/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], "@expo/config-plugins/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], @@ -5365,6 +5492,8 @@ "@onlook/web-template/tailwindcss/postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + "@openapitools/openapi-generator-cli/axios/form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "@react-native/babel-plugin-codegen/@react-native/codegen/hermes-parser/hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="], "@react-native/babel-preset/babel-plugin-syntax-hermes-parser/hermes-parser/hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="], @@ -5403,14 +5532,14 @@ "react-email/ora/log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="], + "react-email/ora/string-width/emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="], + "serve-static/send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "sucrase/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], "sucrase/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "@babel/highlight/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="], "@codesandbox/sdk/ora/cli-cursor/restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], @@ -5435,6 +5564,8 @@ "@onlook/web-template/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "@openapitools/openapi-generator-cli/axios/form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + "expo-constants/@expo/config/@expo/config-plugins/@expo/json-file/json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], "expo-constants/@expo/config/@expo/config-plugins/@expo/json-file/write-file-atomic": ["write-file-atomic@2.4.3", "", { "dependencies": { "graceful-fs": "^4.1.11", "imurmurhash": "^0.1.4", "signal-exit": "^3.0.2" } }, "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ=="], diff --git a/docs/content/docs/contributing/developers/appendix.mdx b/docs/content/docs/contributing/developers/appendix.mdx index 6e8a1739b4..10dc8ce981 100644 --- a/docs/content/docs/contributing/developers/appendix.mdx +++ b/docs/content/docs/contributing/developers/appendix.mdx @@ -6,6 +6,7 @@ description: Appendix for developer setup ## Set up environment variables manually (optional) ##### 1. In `apps/web/client` + Create a copy of the `.env.example` file under the `apps/web/client` directory and name it `.env`. Fill in the values with your own API keys. It should look like this: @@ -23,7 +24,7 @@ SUPABASE_DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:54322/postgres # Anthropic - Enables AI chat. Other providers are optional below ANTHROPIC_API_KEY="" -# Codesandbox - Used to host user apps. Other providers may be supported in the future. May be optional in the future. +# Codesandbox - Used to host user apps. Alternatively, you can use E2B. CSB_API_KEY="" # Fast apply model providers to reliably resolve code changes. Either will work since we will fall back. diff --git a/package.json b/package.json index 973cb98efc..d5fa7858f1 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "dev": "bun --elide-lines 0 --filter @onlook/web dev", "test": "bun --elide-lines 0 --filter '*' test", "docs": "bun --filter @onlook/docs dev", - "backend:start": "bun --filter @onlook/backend start", + "backend:start": "bun --filter @onlook/backend start && (cd apps && docker-compose up -d)", "db:gen": "bun --filter @onlook/db db:gen", "db:push": "bun --filter @onlook/db db:push", "db:seed": "bun --filter @onlook/db db:seed", diff --git a/packages/code-provider/openapitools.json b/packages/code-provider/openapitools.json new file mode 100644 index 0000000000..801fa4a099 --- /dev/null +++ b/packages/code-provider/openapitools.json @@ -0,0 +1,7 @@ +{ + "$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json", + "spaces": 2, + "generator-cli": { + "version": "7.14.0" + } +} diff --git a/packages/code-provider/package.json b/packages/code-provider/package.json index bcfbc68679..37ca49cf98 100644 --- a/packages/code-provider/package.json +++ b/packages/code-provider/package.json @@ -15,7 +15,8 @@ "clean": "rm -rf node_modules", "lint": "eslint --fix .", "format": "prettier --write .", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "generate-coderouter": "openapi-generator-cli generate -i http://localhost:4444/openapi.json -g typescript-fetch -o ./src/providers/coderouter/codegen --skip-validate-spec" }, "keywords": [ "onlook", @@ -28,7 +29,8 @@ "license": "Apache-2.0", "homepage": "https://onlook.com", "devDependencies": { - "@onlook/typescript": "*" + "@onlook/typescript": "*", + "@openapitools/openapi-generator-cli": "^2.22.0" }, "dependencies": { "@codesandbox/sdk": "^1.1.6", diff --git a/packages/code-provider/src/index.ts b/packages/code-provider/src/index.ts index 6b6e16b0c6..b43007a585 100644 --- a/packages/code-provider/src/index.ts +++ b/packages/code-provider/src/index.ts @@ -1,6 +1,7 @@ import { CodeProvider } from './providers'; import { CodesandboxProvider, type CodesandboxProviderOptions } from './providers/codesandbox'; import { NodeFsProvider, type NodeFsProviderOptions } from './providers/nodefs'; +import { CoderouterProvider, type CoderouterProviderOptions } from './providers/coderouter'; export * from './types'; export * from './providers'; @@ -27,6 +28,7 @@ export async function createCodeProviderClient( export interface ProviderInstanceOptions { codesandbox?: CodesandboxProviderOptions; nodefs?: NodeFsProviderOptions; + coderouter?: CoderouterProviderOptions; } function newProviderInstance(codeProvider: CodeProvider, providerOptions: ProviderInstanceOptions) { @@ -44,5 +46,12 @@ function newProviderInstance(codeProvider: CodeProvider, providerOptions: Provid return new NodeFsProvider(providerOptions.nodefs); } + if (codeProvider === CodeProvider.Coderouter) { + if (!providerOptions.coderouter) { + throw new Error('Coderouter provider options are required.'); + } + return new CoderouterProvider(providerOptions.coderouter); + } + throw new Error(`Unimplemented code provider: ${codeProvider}`); } diff --git a/packages/code-provider/src/providers.ts b/packages/code-provider/src/providers.ts index d5887d37ef..2d9e051af4 100644 --- a/packages/code-provider/src/providers.ts +++ b/packages/code-provider/src/providers.ts @@ -5,4 +5,5 @@ export enum CodeProvider { VercelSandbox = 'vercel_sandbox', Modal = 'modal', NodeFs = 'node_fs', + Coderouter = 'coderouter', } diff --git a/packages/code-provider/src/providers/coderouter/codegen/.openapi-generator-ignore b/packages/code-provider/src/providers/coderouter/codegen/.openapi-generator-ignore new file mode 100644 index 0000000000..7484ee590a --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/packages/code-provider/src/providers/coderouter/codegen/.openapi-generator/FILES b/packages/code-provider/src/providers/coderouter/codegen/.openapi-generator/FILES new file mode 100644 index 0000000000..578934e713 --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/.openapi-generator/FILES @@ -0,0 +1,25 @@ +.openapi-generator-ignore +apis/DefaultApi.ts +apis/index.ts +index.ts +models/CoderouterApiAuthSignPost200Response.ts +models/CoderouterApiAuthSignPost401Response.ts +models/CoderouterApiAuthSignPostRequest.ts +models/CoderouterApiSandboxCreatePost200Response.ts +models/CoderouterApiSandboxCreatePostRequest.ts +models/CoderouterApiSandboxFileCopyPostRequest.ts +models/CoderouterApiSandboxFileDeletePostRequest.ts +models/CoderouterApiSandboxFileDownloadPostRequest.ts +models/CoderouterApiSandboxFileListPost200Response.ts +models/CoderouterApiSandboxFileListPost200ResponseFilesInner.ts +models/CoderouterApiSandboxFileListPostRequest.ts +models/CoderouterApiSandboxFileReadPost200Response.ts +models/CoderouterApiSandboxFileReadPostRequest.ts +models/CoderouterApiSandboxFileRenamePostRequest.ts +models/CoderouterApiSandboxFileStatPost200Response.ts +models/CoderouterApiSandboxFileStatPostRequest.ts +models/CoderouterApiSandboxFileWritePostRequest.ts +models/CoderouterApiSandboxFileWritePostRequestFilesInner.ts +models/CoderouterApiSandboxUrlPost200Response.ts +models/index.ts +runtime.ts diff --git a/packages/code-provider/src/providers/coderouter/codegen/.openapi-generator/VERSION b/packages/code-provider/src/providers/coderouter/codegen/.openapi-generator/VERSION new file mode 100644 index 0000000000..e465da4315 --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/.openapi-generator/VERSION @@ -0,0 +1 @@ +7.14.0 diff --git a/packages/code-provider/src/providers/coderouter/codegen/apis/DefaultApi.ts b/packages/code-provider/src/providers/coderouter/codegen/apis/DefaultApi.ts new file mode 100644 index 0000000000..3db9023c3a --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/apis/DefaultApi.ts @@ -0,0 +1,854 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import * as runtime from '../runtime'; +import type { + CoderouterApiAuthSignPost200Response, + CoderouterApiAuthSignPost401Response, + CoderouterApiAuthSignPostRequest, + CoderouterApiSandboxCreatePost200Response, + CoderouterApiSandboxCreatePostRequest, + CoderouterApiSandboxFileCopyPostRequest, + CoderouterApiSandboxFileDeletePostRequest, + CoderouterApiSandboxFileDownloadPostRequest, + CoderouterApiSandboxFileListPost200Response, + CoderouterApiSandboxFileListPostRequest, + CoderouterApiSandboxFileReadPost200Response, + CoderouterApiSandboxFileReadPostRequest, + CoderouterApiSandboxFileRenamePostRequest, + CoderouterApiSandboxFileStatPost200Response, + CoderouterApiSandboxFileStatPostRequest, + CoderouterApiSandboxFileWritePostRequest, + CoderouterApiSandboxUrlPost200Response, +} from '../models/index'; +import { + CoderouterApiAuthSignPost200ResponseFromJSON, + CoderouterApiAuthSignPost200ResponseToJSON, + CoderouterApiAuthSignPost401ResponseFromJSON, + CoderouterApiAuthSignPost401ResponseToJSON, + CoderouterApiAuthSignPostRequestFromJSON, + CoderouterApiAuthSignPostRequestToJSON, + CoderouterApiSandboxCreatePost200ResponseFromJSON, + CoderouterApiSandboxCreatePost200ResponseToJSON, + CoderouterApiSandboxCreatePostRequestFromJSON, + CoderouterApiSandboxCreatePostRequestToJSON, + CoderouterApiSandboxFileCopyPostRequestFromJSON, + CoderouterApiSandboxFileCopyPostRequestToJSON, + CoderouterApiSandboxFileDeletePostRequestFromJSON, + CoderouterApiSandboxFileDeletePostRequestToJSON, + CoderouterApiSandboxFileDownloadPostRequestFromJSON, + CoderouterApiSandboxFileDownloadPostRequestToJSON, + CoderouterApiSandboxFileListPost200ResponseFromJSON, + CoderouterApiSandboxFileListPost200ResponseToJSON, + CoderouterApiSandboxFileListPostRequestFromJSON, + CoderouterApiSandboxFileListPostRequestToJSON, + CoderouterApiSandboxFileReadPost200ResponseFromJSON, + CoderouterApiSandboxFileReadPost200ResponseToJSON, + CoderouterApiSandboxFileReadPostRequestFromJSON, + CoderouterApiSandboxFileReadPostRequestToJSON, + CoderouterApiSandboxFileRenamePostRequestFromJSON, + CoderouterApiSandboxFileRenamePostRequestToJSON, + CoderouterApiSandboxFileStatPost200ResponseFromJSON, + CoderouterApiSandboxFileStatPost200ResponseToJSON, + CoderouterApiSandboxFileStatPostRequestFromJSON, + CoderouterApiSandboxFileStatPostRequestToJSON, + CoderouterApiSandboxFileWritePostRequestFromJSON, + CoderouterApiSandboxFileWritePostRequestToJSON, + CoderouterApiSandboxUrlPost200ResponseFromJSON, + CoderouterApiSandboxUrlPost200ResponseToJSON, +} from '../models/index'; + +export interface CoderouterApiAuthSignPostOperationRequest { + coderouterApiAuthSignPostRequest?: CoderouterApiAuthSignPostRequest; +} + +export interface CoderouterApiSandboxCreatePostOperationRequest { + coderouterApiSandboxCreatePostRequest?: CoderouterApiSandboxCreatePostRequest; +} + +export interface CoderouterApiSandboxFileCopyPostOperationRequest { + coderouterApiSandboxFileCopyPostRequest?: CoderouterApiSandboxFileCopyPostRequest; +} + +export interface CoderouterApiSandboxFileDeletePostOperationRequest { + coderouterApiSandboxFileDeletePostRequest?: CoderouterApiSandboxFileDeletePostRequest; +} + +export interface CoderouterApiSandboxFileDownloadPostOperationRequest { + coderouterApiSandboxFileDownloadPostRequest?: CoderouterApiSandboxFileDownloadPostRequest; +} + +export interface CoderouterApiSandboxFileListPostOperationRequest { + coderouterApiSandboxFileListPostRequest?: CoderouterApiSandboxFileListPostRequest; +} + +export interface CoderouterApiSandboxFileReadPostOperationRequest { + coderouterApiSandboxFileReadPostRequest?: CoderouterApiSandboxFileReadPostRequest; +} + +export interface CoderouterApiSandboxFileRenamePostOperationRequest { + coderouterApiSandboxFileRenamePostRequest?: CoderouterApiSandboxFileRenamePostRequest; +} + +export interface CoderouterApiSandboxFileStatPostOperationRequest { + coderouterApiSandboxFileStatPostRequest?: CoderouterApiSandboxFileStatPostRequest; +} + +export interface CoderouterApiSandboxFileWritePostOperationRequest { + coderouterApiSandboxFileWritePostRequest?: CoderouterApiSandboxFileWritePostRequest; +} + +export interface CoderouterApiSandboxPausePostRequest { + body?: object; +} + +export interface CoderouterApiSandboxResumePostRequest { + body?: object; +} + +export interface CoderouterApiSandboxStopPostRequest { + body?: object; +} + +/** + * + */ +export class DefaultApi extends runtime.BaseAPI { + /** + */ + async coderouterApiAuthSignPostRaw( + requestParameters: CoderouterApiAuthSignPostOperationRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json'; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token('apikey', []); + + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } + } + + let urlPath = `/coderouter/api/auth/sign`; + + const response = await this.request( + { + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: CoderouterApiAuthSignPostRequestToJSON( + requestParameters['coderouterApiAuthSignPostRequest'], + ), + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response, (jsonValue) => + CoderouterApiAuthSignPost200ResponseFromJSON(jsonValue), + ); + } + + /** + */ + async coderouterApiAuthSignPost( + requestParameters: CoderouterApiAuthSignPostOperationRequest = {}, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.coderouterApiAuthSignPostRaw(requestParameters, initOverrides); + return await response.value(); + } + + /** + */ + async coderouterApiSandboxCreatePostRaw( + requestParameters: CoderouterApiSandboxCreatePostOperationRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json'; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token('jwt', []); + + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } + } + + let urlPath = `/coderouter/api/sandbox/create`; + + const response = await this.request( + { + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: CoderouterApiSandboxCreatePostRequestToJSON( + requestParameters['coderouterApiSandboxCreatePostRequest'], + ), + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response, (jsonValue) => + CoderouterApiSandboxCreatePost200ResponseFromJSON(jsonValue), + ); + } + + /** + */ + async coderouterApiSandboxCreatePost( + requestParameters: CoderouterApiSandboxCreatePostOperationRequest = {}, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.coderouterApiSandboxCreatePostRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + + /** + */ + async coderouterApiSandboxFileCopyPostRaw( + requestParameters: CoderouterApiSandboxFileCopyPostOperationRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json'; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token('jwt', []); + + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } + } + + let urlPath = `/coderouter/api/sandbox/file/copy`; + + const response = await this.request( + { + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: CoderouterApiSandboxFileCopyPostRequestToJSON( + requestParameters['coderouterApiSandboxFileCopyPostRequest'], + ), + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response); + } + + /** + */ + async coderouterApiSandboxFileCopyPost( + requestParameters: CoderouterApiSandboxFileCopyPostOperationRequest = {}, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.coderouterApiSandboxFileCopyPostRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + + /** + */ + async coderouterApiSandboxFileDeletePostRaw( + requestParameters: CoderouterApiSandboxFileDeletePostOperationRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json'; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token('jwt', []); + + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } + } + + let urlPath = `/coderouter/api/sandbox/file/delete`; + + const response = await this.request( + { + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: CoderouterApiSandboxFileDeletePostRequestToJSON( + requestParameters['coderouterApiSandboxFileDeletePostRequest'], + ), + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response); + } + + /** + */ + async coderouterApiSandboxFileDeletePost( + requestParameters: CoderouterApiSandboxFileDeletePostOperationRequest = {}, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.coderouterApiSandboxFileDeletePostRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + + /** + */ + async coderouterApiSandboxFileDownloadPostRaw( + requestParameters: CoderouterApiSandboxFileDownloadPostOperationRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json'; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token('jwt', []); + + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } + } + + let urlPath = `/coderouter/api/sandbox/file/download`; + + const response = await this.request( + { + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: CoderouterApiSandboxFileDownloadPostRequestToJSON( + requestParameters['coderouterApiSandboxFileDownloadPostRequest'], + ), + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response); + } + + /** + */ + async coderouterApiSandboxFileDownloadPost( + requestParameters: CoderouterApiSandboxFileDownloadPostOperationRequest = {}, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.coderouterApiSandboxFileDownloadPostRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + + /** + */ + async coderouterApiSandboxFileListPostRaw( + requestParameters: CoderouterApiSandboxFileListPostOperationRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json'; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token('jwt', []); + + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } + } + + let urlPath = `/coderouter/api/sandbox/file/list`; + + const response = await this.request( + { + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: CoderouterApiSandboxFileListPostRequestToJSON( + requestParameters['coderouterApiSandboxFileListPostRequest'], + ), + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response, (jsonValue) => + CoderouterApiSandboxFileListPost200ResponseFromJSON(jsonValue), + ); + } + + /** + */ + async coderouterApiSandboxFileListPost( + requestParameters: CoderouterApiSandboxFileListPostOperationRequest = {}, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.coderouterApiSandboxFileListPostRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + + /** + */ + async coderouterApiSandboxFileReadPostRaw( + requestParameters: CoderouterApiSandboxFileReadPostOperationRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json'; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token('jwt', []); + + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } + } + + let urlPath = `/coderouter/api/sandbox/file/read`; + + const response = await this.request( + { + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: CoderouterApiSandboxFileReadPostRequestToJSON( + requestParameters['coderouterApiSandboxFileReadPostRequest'], + ), + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response, (jsonValue) => + CoderouterApiSandboxFileReadPost200ResponseFromJSON(jsonValue), + ); + } + + /** + */ + async coderouterApiSandboxFileReadPost( + requestParameters: CoderouterApiSandboxFileReadPostOperationRequest = {}, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.coderouterApiSandboxFileReadPostRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + + /** + */ + async coderouterApiSandboxFileRenamePostRaw( + requestParameters: CoderouterApiSandboxFileRenamePostOperationRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json'; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token('jwt', []); + + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } + } + + let urlPath = `/coderouter/api/sandbox/file/rename`; + + const response = await this.request( + { + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: CoderouterApiSandboxFileRenamePostRequestToJSON( + requestParameters['coderouterApiSandboxFileRenamePostRequest'], + ), + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response); + } + + /** + */ + async coderouterApiSandboxFileRenamePost( + requestParameters: CoderouterApiSandboxFileRenamePostOperationRequest = {}, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.coderouterApiSandboxFileRenamePostRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + + /** + */ + async coderouterApiSandboxFileStatPostRaw( + requestParameters: CoderouterApiSandboxFileStatPostOperationRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json'; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token('jwt', []); + + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } + } + + let urlPath = `/coderouter/api/sandbox/file/stat`; + + const response = await this.request( + { + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: CoderouterApiSandboxFileStatPostRequestToJSON( + requestParameters['coderouterApiSandboxFileStatPostRequest'], + ), + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response, (jsonValue) => + CoderouterApiSandboxFileStatPost200ResponseFromJSON(jsonValue), + ); + } + + /** + */ + async coderouterApiSandboxFileStatPost( + requestParameters: CoderouterApiSandboxFileStatPostOperationRequest = {}, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.coderouterApiSandboxFileStatPostRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + + /** + */ + async coderouterApiSandboxFileWritePostRaw( + requestParameters: CoderouterApiSandboxFileWritePostOperationRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json'; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token('jwt', []); + + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } + } + + let urlPath = `/coderouter/api/sandbox/file/write`; + + const response = await this.request( + { + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: CoderouterApiSandboxFileWritePostRequestToJSON( + requestParameters['coderouterApiSandboxFileWritePostRequest'], + ), + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response); + } + + /** + */ + async coderouterApiSandboxFileWritePost( + requestParameters: CoderouterApiSandboxFileWritePostOperationRequest = {}, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.coderouterApiSandboxFileWritePostRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + + /** + */ + async coderouterApiSandboxPausePostRaw( + requestParameters: CoderouterApiSandboxPausePostRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json'; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token('jwt', []); + + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } + } + + let urlPath = `/coderouter/api/sandbox/pause`; + + const response = await this.request( + { + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: requestParameters['body'] as any, + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response, (jsonValue) => + CoderouterApiSandboxCreatePost200ResponseFromJSON(jsonValue), + ); + } + + /** + */ + async coderouterApiSandboxPausePost( + requestParameters: CoderouterApiSandboxPausePostRequest = {}, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.coderouterApiSandboxPausePostRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + + /** + */ + async coderouterApiSandboxResumePostRaw( + requestParameters: CoderouterApiSandboxResumePostRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json'; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token('jwt', []); + + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } + } + + let urlPath = `/coderouter/api/sandbox/resume`; + + const response = await this.request( + { + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: requestParameters['body'] as any, + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response, (jsonValue) => + CoderouterApiSandboxCreatePost200ResponseFromJSON(jsonValue), + ); + } + + /** + */ + async coderouterApiSandboxResumePost( + requestParameters: CoderouterApiSandboxResumePostRequest = {}, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.coderouterApiSandboxResumePostRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + + /** + */ + async coderouterApiSandboxStopPostRaw( + requestParameters: CoderouterApiSandboxStopPostRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json'; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token('jwt', []); + + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } + } + + let urlPath = `/coderouter/api/sandbox/stop`; + + const response = await this.request( + { + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: requestParameters['body'] as any, + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response, (jsonValue) => + CoderouterApiSandboxCreatePost200ResponseFromJSON(jsonValue), + ); + } + + /** + */ + async coderouterApiSandboxStopPost( + requestParameters: CoderouterApiSandboxStopPostRequest = {}, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.coderouterApiSandboxStopPostRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + + /** + */ + async coderouterApiSandboxUrlPostRaw( + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token('jwt', []); + + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } + } + + let urlPath = `/coderouter/api/sandbox/url`; + + const response = await this.request( + { + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response, (jsonValue) => + CoderouterApiSandboxUrlPost200ResponseFromJSON(jsonValue), + ); + } + + /** + */ + async coderouterApiSandboxUrlPost( + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.coderouterApiSandboxUrlPostRaw(initOverrides); + return await response.value(); + } +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/apis/index.ts b/packages/code-provider/src/providers/coderouter/codegen/apis/index.ts new file mode 100644 index 0000000000..69c44c00fa --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/apis/index.ts @@ -0,0 +1,3 @@ +/* tslint:disable */ +/* eslint-disable */ +export * from './DefaultApi'; diff --git a/packages/code-provider/src/providers/coderouter/codegen/index.ts b/packages/code-provider/src/providers/coderouter/codegen/index.ts new file mode 100644 index 0000000000..bebe8bbbe2 --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/index.ts @@ -0,0 +1,5 @@ +/* tslint:disable */ +/* eslint-disable */ +export * from './runtime'; +export * from './apis/index'; +export * from './models/index'; diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiAuthSignPost200Response.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiAuthSignPost200Response.ts new file mode 100644 index 0000000000..ce950e070f --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiAuthSignPost200Response.ts @@ -0,0 +1,75 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface CoderouterApiAuthSignPost200Response + */ +export interface CoderouterApiAuthSignPost200Response { + /** + * The JWT token to send when interacting with the API as header "X-Auth-Jwt.". + * @type {string} + * @memberof CoderouterApiAuthSignPost200Response + */ + jwt: string; +} + +/** + * Check if a given object implements the CoderouterApiAuthSignPost200Response interface. + */ +export function instanceOfCoderouterApiAuthSignPost200Response( + value: object, +): value is CoderouterApiAuthSignPost200Response { + if (!('jwt' in value) || value['jwt'] === undefined) return false; + return true; +} + +export function CoderouterApiAuthSignPost200ResponseFromJSON( + json: any, +): CoderouterApiAuthSignPost200Response { + return CoderouterApiAuthSignPost200ResponseFromJSONTyped(json, false); +} + +export function CoderouterApiAuthSignPost200ResponseFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiAuthSignPost200Response { + if (json == null) { + return json; + } + return { + jwt: json['jwt'], + }; +} + +export function CoderouterApiAuthSignPost200ResponseToJSON( + json: any, +): CoderouterApiAuthSignPost200Response { + return CoderouterApiAuthSignPost200ResponseToJSONTyped(json, false); +} + +export function CoderouterApiAuthSignPost200ResponseToJSONTyped( + value?: CoderouterApiAuthSignPost200Response | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + jwt: value['jwt'], + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiAuthSignPost401Response.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiAuthSignPost401Response.ts new file mode 100644 index 0000000000..77ce1b48aa --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiAuthSignPost401Response.ts @@ -0,0 +1,75 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface CoderouterApiAuthSignPost401Response + */ +export interface CoderouterApiAuthSignPost401Response { + /** + * + * @type {string} + * @memberof CoderouterApiAuthSignPost401Response + */ + error: string; +} + +/** + * Check if a given object implements the CoderouterApiAuthSignPost401Response interface. + */ +export function instanceOfCoderouterApiAuthSignPost401Response( + value: object, +): value is CoderouterApiAuthSignPost401Response { + if (!('error' in value) || value['error'] === undefined) return false; + return true; +} + +export function CoderouterApiAuthSignPost401ResponseFromJSON( + json: any, +): CoderouterApiAuthSignPost401Response { + return CoderouterApiAuthSignPost401ResponseFromJSONTyped(json, false); +} + +export function CoderouterApiAuthSignPost401ResponseFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiAuthSignPost401Response { + if (json == null) { + return json; + } + return { + error: json['error'], + }; +} + +export function CoderouterApiAuthSignPost401ResponseToJSON( + json: any, +): CoderouterApiAuthSignPost401Response { + return CoderouterApiAuthSignPost401ResponseToJSONTyped(json, false); +} + +export function CoderouterApiAuthSignPost401ResponseToJSONTyped( + value?: CoderouterApiAuthSignPost401Response | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + error: value['error'], + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiAuthSignPostRequest.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiAuthSignPostRequest.ts new file mode 100644 index 0000000000..99c6ed61ed --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiAuthSignPostRequest.ts @@ -0,0 +1,82 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface CoderouterApiAuthSignPostRequest + */ +export interface CoderouterApiAuthSignPostRequest { + /** + * The ID of the sandbox. This is your own ID. It can be a UUID or any unique string. Required when using a sandbox. + * @type {string} + * @memberof CoderouterApiAuthSignPostRequest + */ + sandboxId?: string; + /** + * The ID of the user. This is your own ID. It can be a UUID or any unique string. + * @type {string} + * @memberof CoderouterApiAuthSignPostRequest + */ + userId?: string; +} + +/** + * Check if a given object implements the CoderouterApiAuthSignPostRequest interface. + */ +export function instanceOfCoderouterApiAuthSignPostRequest( + value: object, +): value is CoderouterApiAuthSignPostRequest { + return true; +} + +export function CoderouterApiAuthSignPostRequestFromJSON( + json: any, +): CoderouterApiAuthSignPostRequest { + return CoderouterApiAuthSignPostRequestFromJSONTyped(json, false); +} + +export function CoderouterApiAuthSignPostRequestFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiAuthSignPostRequest { + if (json == null) { + return json; + } + return { + sandboxId: json['sandboxId'] == null ? undefined : json['sandboxId'], + userId: json['userId'] == null ? undefined : json['userId'], + }; +} + +export function CoderouterApiAuthSignPostRequestToJSON( + json: any, +): CoderouterApiAuthSignPostRequest { + return CoderouterApiAuthSignPostRequestToJSONTyped(json, false); +} + +export function CoderouterApiAuthSignPostRequestToJSONTyped( + value?: CoderouterApiAuthSignPostRequest | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + sandboxId: value['sandboxId'], + userId: value['userId'], + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxCreatePost200Response.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxCreatePost200Response.ts new file mode 100644 index 0000000000..7ec224e4c6 --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxCreatePost200Response.ts @@ -0,0 +1,75 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface CoderouterApiSandboxCreatePost200Response + */ +export interface CoderouterApiSandboxCreatePost200Response { + /** + * The ID of the sandbox. This is your own ID. It can be a UUID or any unique string. + * @type {string} + * @memberof CoderouterApiSandboxCreatePost200Response + */ + id: string; +} + +/** + * Check if a given object implements the CoderouterApiSandboxCreatePost200Response interface. + */ +export function instanceOfCoderouterApiSandboxCreatePost200Response( + value: object, +): value is CoderouterApiSandboxCreatePost200Response { + if (!('id' in value) || value['id'] === undefined) return false; + return true; +} + +export function CoderouterApiSandboxCreatePost200ResponseFromJSON( + json: any, +): CoderouterApiSandboxCreatePost200Response { + return CoderouterApiSandboxCreatePost200ResponseFromJSONTyped(json, false); +} + +export function CoderouterApiSandboxCreatePost200ResponseFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiSandboxCreatePost200Response { + if (json == null) { + return json; + } + return { + id: json['id'], + }; +} + +export function CoderouterApiSandboxCreatePost200ResponseToJSON( + json: any, +): CoderouterApiSandboxCreatePost200Response { + return CoderouterApiSandboxCreatePost200ResponseToJSONTyped(json, false); +} + +export function CoderouterApiSandboxCreatePost200ResponseToJSONTyped( + value?: CoderouterApiSandboxCreatePost200Response | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + id: value['id'], + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxCreatePostRequest.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxCreatePostRequest.ts new file mode 100644 index 0000000000..c7bb57fc8b --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxCreatePostRequest.ts @@ -0,0 +1,84 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface CoderouterApiSandboxCreatePostRequest + */ +export interface CoderouterApiSandboxCreatePostRequest { + /** + * The ID of the template to use for the sandbox. + * @type {string} + * @memberof CoderouterApiSandboxCreatePostRequest + */ + templateId: string; + /** + * The metadata of the sandbox. + * @type {{ [key: string]: string; }} + * @memberof CoderouterApiSandboxCreatePostRequest + */ + metadata: { [key: string]: string }; +} + +/** + * Check if a given object implements the CoderouterApiSandboxCreatePostRequest interface. + */ +export function instanceOfCoderouterApiSandboxCreatePostRequest( + value: object, +): value is CoderouterApiSandboxCreatePostRequest { + if (!('templateId' in value) || value['templateId'] === undefined) return false; + if (!('metadata' in value) || value['metadata'] === undefined) return false; + return true; +} + +export function CoderouterApiSandboxCreatePostRequestFromJSON( + json: any, +): CoderouterApiSandboxCreatePostRequest { + return CoderouterApiSandboxCreatePostRequestFromJSONTyped(json, false); +} + +export function CoderouterApiSandboxCreatePostRequestFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiSandboxCreatePostRequest { + if (json == null) { + return json; + } + return { + templateId: json['templateId'], + metadata: json['metadata'], + }; +} + +export function CoderouterApiSandboxCreatePostRequestToJSON( + json: any, +): CoderouterApiSandboxCreatePostRequest { + return CoderouterApiSandboxCreatePostRequestToJSONTyped(json, false); +} + +export function CoderouterApiSandboxCreatePostRequestToJSONTyped( + value?: CoderouterApiSandboxCreatePostRequest | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + templateId: value['templateId'], + metadata: value['metadata'], + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileCopyPostRequest.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileCopyPostRequest.ts new file mode 100644 index 0000000000..ad1bab4745 --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileCopyPostRequest.ts @@ -0,0 +1,102 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface CoderouterApiSandboxFileCopyPostRequest + */ +export interface CoderouterApiSandboxFileCopyPostRequest { + /** + * The path of the source file. + * @type {string} + * @memberof CoderouterApiSandboxFileCopyPostRequest + */ + source: string; + /** + * The path of the destination file. + * @type {string} + * @memberof CoderouterApiSandboxFileCopyPostRequest + */ + destination: string; + /** + * Whether to overwrite the destination file if it already exists. + * @type {boolean} + * @memberof CoderouterApiSandboxFileCopyPostRequest + */ + overwrite: boolean; + /** + * Whether to copy the file recursively. + * @type {boolean} + * @memberof CoderouterApiSandboxFileCopyPostRequest + */ + recursive: boolean; +} + +/** + * Check if a given object implements the CoderouterApiSandboxFileCopyPostRequest interface. + */ +export function instanceOfCoderouterApiSandboxFileCopyPostRequest( + value: object, +): value is CoderouterApiSandboxFileCopyPostRequest { + if (!('source' in value) || value['source'] === undefined) return false; + if (!('destination' in value) || value['destination'] === undefined) return false; + if (!('overwrite' in value) || value['overwrite'] === undefined) return false; + if (!('recursive' in value) || value['recursive'] === undefined) return false; + return true; +} + +export function CoderouterApiSandboxFileCopyPostRequestFromJSON( + json: any, +): CoderouterApiSandboxFileCopyPostRequest { + return CoderouterApiSandboxFileCopyPostRequestFromJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileCopyPostRequestFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiSandboxFileCopyPostRequest { + if (json == null) { + return json; + } + return { + source: json['source'], + destination: json['destination'], + overwrite: json['overwrite'], + recursive: json['recursive'], + }; +} + +export function CoderouterApiSandboxFileCopyPostRequestToJSON( + json: any, +): CoderouterApiSandboxFileCopyPostRequest { + return CoderouterApiSandboxFileCopyPostRequestToJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileCopyPostRequestToJSONTyped( + value?: CoderouterApiSandboxFileCopyPostRequest | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + source: value['source'], + destination: value['destination'], + overwrite: value['overwrite'], + recursive: value['recursive'], + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileDeletePostRequest.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileDeletePostRequest.ts new file mode 100644 index 0000000000..26fa203cc9 --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileDeletePostRequest.ts @@ -0,0 +1,75 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface CoderouterApiSandboxFileDeletePostRequest + */ +export interface CoderouterApiSandboxFileDeletePostRequest { + /** + * The path of the file to delete. + * @type {string} + * @memberof CoderouterApiSandboxFileDeletePostRequest + */ + path: string; +} + +/** + * Check if a given object implements the CoderouterApiSandboxFileDeletePostRequest interface. + */ +export function instanceOfCoderouterApiSandboxFileDeletePostRequest( + value: object, +): value is CoderouterApiSandboxFileDeletePostRequest { + if (!('path' in value) || value['path'] === undefined) return false; + return true; +} + +export function CoderouterApiSandboxFileDeletePostRequestFromJSON( + json: any, +): CoderouterApiSandboxFileDeletePostRequest { + return CoderouterApiSandboxFileDeletePostRequestFromJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileDeletePostRequestFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiSandboxFileDeletePostRequest { + if (json == null) { + return json; + } + return { + path: json['path'], + }; +} + +export function CoderouterApiSandboxFileDeletePostRequestToJSON( + json: any, +): CoderouterApiSandboxFileDeletePostRequest { + return CoderouterApiSandboxFileDeletePostRequestToJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileDeletePostRequestToJSONTyped( + value?: CoderouterApiSandboxFileDeletePostRequest | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + path: value['path'], + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileDownloadPostRequest.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileDownloadPostRequest.ts new file mode 100644 index 0000000000..9a036c58c5 --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileDownloadPostRequest.ts @@ -0,0 +1,75 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface CoderouterApiSandboxFileDownloadPostRequest + */ +export interface CoderouterApiSandboxFileDownloadPostRequest { + /** + * The path of the file to download. + * @type {string} + * @memberof CoderouterApiSandboxFileDownloadPostRequest + */ + path: string; +} + +/** + * Check if a given object implements the CoderouterApiSandboxFileDownloadPostRequest interface. + */ +export function instanceOfCoderouterApiSandboxFileDownloadPostRequest( + value: object, +): value is CoderouterApiSandboxFileDownloadPostRequest { + if (!('path' in value) || value['path'] === undefined) return false; + return true; +} + +export function CoderouterApiSandboxFileDownloadPostRequestFromJSON( + json: any, +): CoderouterApiSandboxFileDownloadPostRequest { + return CoderouterApiSandboxFileDownloadPostRequestFromJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileDownloadPostRequestFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiSandboxFileDownloadPostRequest { + if (json == null) { + return json; + } + return { + path: json['path'], + }; +} + +export function CoderouterApiSandboxFileDownloadPostRequestToJSON( + json: any, +): CoderouterApiSandboxFileDownloadPostRequest { + return CoderouterApiSandboxFileDownloadPostRequestToJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileDownloadPostRequestToJSONTyped( + value?: CoderouterApiSandboxFileDownloadPostRequest | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + path: value['path'], + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileListPost200Response.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileListPost200Response.ts new file mode 100644 index 0000000000..ab12c6931c --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileListPost200Response.ts @@ -0,0 +1,87 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +import type { CoderouterApiSandboxFileListPost200ResponseFilesInner } from './CoderouterApiSandboxFileListPost200ResponseFilesInner'; +import { + CoderouterApiSandboxFileListPost200ResponseFilesInnerFromJSON, + CoderouterApiSandboxFileListPost200ResponseFilesInnerFromJSONTyped, + CoderouterApiSandboxFileListPost200ResponseFilesInnerToJSON, + CoderouterApiSandboxFileListPost200ResponseFilesInnerToJSONTyped, +} from './CoderouterApiSandboxFileListPost200ResponseFilesInner'; + +/** + * + * @export + * @interface CoderouterApiSandboxFileListPost200Response + */ +export interface CoderouterApiSandboxFileListPost200Response { + /** + * + * @type {Array} + * @memberof CoderouterApiSandboxFileListPost200Response + */ + files: Array; +} + +/** + * Check if a given object implements the CoderouterApiSandboxFileListPost200Response interface. + */ +export function instanceOfCoderouterApiSandboxFileListPost200Response( + value: object, +): value is CoderouterApiSandboxFileListPost200Response { + if (!('files' in value) || value['files'] === undefined) return false; + return true; +} + +export function CoderouterApiSandboxFileListPost200ResponseFromJSON( + json: any, +): CoderouterApiSandboxFileListPost200Response { + return CoderouterApiSandboxFileListPost200ResponseFromJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileListPost200ResponseFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiSandboxFileListPost200Response { + if (json == null) { + return json; + } + return { + files: (json['files'] as Array).map( + CoderouterApiSandboxFileListPost200ResponseFilesInnerFromJSON, + ), + }; +} + +export function CoderouterApiSandboxFileListPost200ResponseToJSON( + json: any, +): CoderouterApiSandboxFileListPost200Response { + return CoderouterApiSandboxFileListPost200ResponseToJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileListPost200ResponseToJSONTyped( + value?: CoderouterApiSandboxFileListPost200Response | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + files: (value['files'] as Array).map( + CoderouterApiSandboxFileListPost200ResponseFilesInnerToJSON, + ), + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileListPost200ResponseFilesInner.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileListPost200ResponseFilesInner.ts new file mode 100644 index 0000000000..0af8e4285a --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileListPost200ResponseFilesInner.ts @@ -0,0 +1,103 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface CoderouterApiSandboxFileListPost200ResponseFilesInner + */ +export interface CoderouterApiSandboxFileListPost200ResponseFilesInner { + /** + * + * @type {string} + * @memberof CoderouterApiSandboxFileListPost200ResponseFilesInner + */ + name: string; + /** + * + * @type {string} + * @memberof CoderouterApiSandboxFileListPost200ResponseFilesInner + */ + path: string; + /** + * + * @type {string} + * @memberof CoderouterApiSandboxFileListPost200ResponseFilesInner + */ + type: CoderouterApiSandboxFileListPost200ResponseFilesInnerTypeEnum; +} + +/** + * @export + */ +export const CoderouterApiSandboxFileListPost200ResponseFilesInnerTypeEnum = { + File: 'file', + Directory: 'directory', +} as const; +export type CoderouterApiSandboxFileListPost200ResponseFilesInnerTypeEnum = + (typeof CoderouterApiSandboxFileListPost200ResponseFilesInnerTypeEnum)[keyof typeof CoderouterApiSandboxFileListPost200ResponseFilesInnerTypeEnum]; + +/** + * Check if a given object implements the CoderouterApiSandboxFileListPost200ResponseFilesInner interface. + */ +export function instanceOfCoderouterApiSandboxFileListPost200ResponseFilesInner( + value: object, +): value is CoderouterApiSandboxFileListPost200ResponseFilesInner { + if (!('name' in value) || value['name'] === undefined) return false; + if (!('path' in value) || value['path'] === undefined) return false; + if (!('type' in value) || value['type'] === undefined) return false; + return true; +} + +export function CoderouterApiSandboxFileListPost200ResponseFilesInnerFromJSON( + json: any, +): CoderouterApiSandboxFileListPost200ResponseFilesInner { + return CoderouterApiSandboxFileListPost200ResponseFilesInnerFromJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileListPost200ResponseFilesInnerFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiSandboxFileListPost200ResponseFilesInner { + if (json == null) { + return json; + } + return { + name: json['name'], + path: json['path'], + type: json['type'], + }; +} + +export function CoderouterApiSandboxFileListPost200ResponseFilesInnerToJSON( + json: any, +): CoderouterApiSandboxFileListPost200ResponseFilesInner { + return CoderouterApiSandboxFileListPost200ResponseFilesInnerToJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileListPost200ResponseFilesInnerToJSONTyped( + value?: CoderouterApiSandboxFileListPost200ResponseFilesInner | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + name: value['name'], + path: value['path'], + type: value['type'], + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileListPostRequest.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileListPostRequest.ts new file mode 100644 index 0000000000..f0cabf12a7 --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileListPostRequest.ts @@ -0,0 +1,75 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface CoderouterApiSandboxFileListPostRequest + */ +export interface CoderouterApiSandboxFileListPostRequest { + /** + * The path of the directory to list. + * @type {string} + * @memberof CoderouterApiSandboxFileListPostRequest + */ + path: string; +} + +/** + * Check if a given object implements the CoderouterApiSandboxFileListPostRequest interface. + */ +export function instanceOfCoderouterApiSandboxFileListPostRequest( + value: object, +): value is CoderouterApiSandboxFileListPostRequest { + if (!('path' in value) || value['path'] === undefined) return false; + return true; +} + +export function CoderouterApiSandboxFileListPostRequestFromJSON( + json: any, +): CoderouterApiSandboxFileListPostRequest { + return CoderouterApiSandboxFileListPostRequestFromJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileListPostRequestFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiSandboxFileListPostRequest { + if (json == null) { + return json; + } + return { + path: json['path'], + }; +} + +export function CoderouterApiSandboxFileListPostRequestToJSON( + json: any, +): CoderouterApiSandboxFileListPostRequest { + return CoderouterApiSandboxFileListPostRequestToJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileListPostRequestToJSONTyped( + value?: CoderouterApiSandboxFileListPostRequest | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + path: value['path'], + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileReadPost200Response.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileReadPost200Response.ts new file mode 100644 index 0000000000..45bc45beb9 --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileReadPost200Response.ts @@ -0,0 +1,75 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface CoderouterApiSandboxFileReadPost200Response + */ +export interface CoderouterApiSandboxFileReadPost200Response { + /** + * + * @type {string} + * @memberof CoderouterApiSandboxFileReadPost200Response + */ + data: string; +} + +/** + * Check if a given object implements the CoderouterApiSandboxFileReadPost200Response interface. + */ +export function instanceOfCoderouterApiSandboxFileReadPost200Response( + value: object, +): value is CoderouterApiSandboxFileReadPost200Response { + if (!('data' in value) || value['data'] === undefined) return false; + return true; +} + +export function CoderouterApiSandboxFileReadPost200ResponseFromJSON( + json: any, +): CoderouterApiSandboxFileReadPost200Response { + return CoderouterApiSandboxFileReadPost200ResponseFromJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileReadPost200ResponseFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiSandboxFileReadPost200Response { + if (json == null) { + return json; + } + return { + data: json['data'], + }; +} + +export function CoderouterApiSandboxFileReadPost200ResponseToJSON( + json: any, +): CoderouterApiSandboxFileReadPost200Response { + return CoderouterApiSandboxFileReadPost200ResponseToJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileReadPost200ResponseToJSONTyped( + value?: CoderouterApiSandboxFileReadPost200Response | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + data: value['data'], + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileReadPostRequest.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileReadPostRequest.ts new file mode 100644 index 0000000000..84687a7f6f --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileReadPostRequest.ts @@ -0,0 +1,75 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface CoderouterApiSandboxFileReadPostRequest + */ +export interface CoderouterApiSandboxFileReadPostRequest { + /** + * The path of the file to read. + * @type {string} + * @memberof CoderouterApiSandboxFileReadPostRequest + */ + path: string; +} + +/** + * Check if a given object implements the CoderouterApiSandboxFileReadPostRequest interface. + */ +export function instanceOfCoderouterApiSandboxFileReadPostRequest( + value: object, +): value is CoderouterApiSandboxFileReadPostRequest { + if (!('path' in value) || value['path'] === undefined) return false; + return true; +} + +export function CoderouterApiSandboxFileReadPostRequestFromJSON( + json: any, +): CoderouterApiSandboxFileReadPostRequest { + return CoderouterApiSandboxFileReadPostRequestFromJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileReadPostRequestFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiSandboxFileReadPostRequest { + if (json == null) { + return json; + } + return { + path: json['path'], + }; +} + +export function CoderouterApiSandboxFileReadPostRequestToJSON( + json: any, +): CoderouterApiSandboxFileReadPostRequest { + return CoderouterApiSandboxFileReadPostRequestToJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileReadPostRequestToJSONTyped( + value?: CoderouterApiSandboxFileReadPostRequest | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + path: value['path'], + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileRenamePostRequest.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileRenamePostRequest.ts new file mode 100644 index 0000000000..62597faf6b --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileRenamePostRequest.ts @@ -0,0 +1,84 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface CoderouterApiSandboxFileRenamePostRequest + */ +export interface CoderouterApiSandboxFileRenamePostRequest { + /** + * The path of the file to rename. + * @type {string} + * @memberof CoderouterApiSandboxFileRenamePostRequest + */ + oldPath: string; + /** + * The new path of the file. + * @type {string} + * @memberof CoderouterApiSandboxFileRenamePostRequest + */ + newPath: string; +} + +/** + * Check if a given object implements the CoderouterApiSandboxFileRenamePostRequest interface. + */ +export function instanceOfCoderouterApiSandboxFileRenamePostRequest( + value: object, +): value is CoderouterApiSandboxFileRenamePostRequest { + if (!('oldPath' in value) || value['oldPath'] === undefined) return false; + if (!('newPath' in value) || value['newPath'] === undefined) return false; + return true; +} + +export function CoderouterApiSandboxFileRenamePostRequestFromJSON( + json: any, +): CoderouterApiSandboxFileRenamePostRequest { + return CoderouterApiSandboxFileRenamePostRequestFromJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileRenamePostRequestFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiSandboxFileRenamePostRequest { + if (json == null) { + return json; + } + return { + oldPath: json['oldPath'], + newPath: json['newPath'], + }; +} + +export function CoderouterApiSandboxFileRenamePostRequestToJSON( + json: any, +): CoderouterApiSandboxFileRenamePostRequest { + return CoderouterApiSandboxFileRenamePostRequestToJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileRenamePostRequestToJSONTyped( + value?: CoderouterApiSandboxFileRenamePostRequest | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + oldPath: value['oldPath'], + newPath: value['newPath'], + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileStatPost200Response.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileStatPost200Response.ts new file mode 100644 index 0000000000..69374196a1 --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileStatPost200Response.ts @@ -0,0 +1,85 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface CoderouterApiSandboxFileStatPost200Response + */ +export interface CoderouterApiSandboxFileStatPost200Response { + /** + * + * @type {string} + * @memberof CoderouterApiSandboxFileStatPost200Response + */ + type: CoderouterApiSandboxFileStatPost200ResponseTypeEnum; +} + +/** + * @export + */ +export const CoderouterApiSandboxFileStatPost200ResponseTypeEnum = { + File: 'file', + Directory: 'directory', +} as const; +export type CoderouterApiSandboxFileStatPost200ResponseTypeEnum = + (typeof CoderouterApiSandboxFileStatPost200ResponseTypeEnum)[keyof typeof CoderouterApiSandboxFileStatPost200ResponseTypeEnum]; + +/** + * Check if a given object implements the CoderouterApiSandboxFileStatPost200Response interface. + */ +export function instanceOfCoderouterApiSandboxFileStatPost200Response( + value: object, +): value is CoderouterApiSandboxFileStatPost200Response { + if (!('type' in value) || value['type'] === undefined) return false; + return true; +} + +export function CoderouterApiSandboxFileStatPost200ResponseFromJSON( + json: any, +): CoderouterApiSandboxFileStatPost200Response { + return CoderouterApiSandboxFileStatPost200ResponseFromJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileStatPost200ResponseFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiSandboxFileStatPost200Response { + if (json == null) { + return json; + } + return { + type: json['type'], + }; +} + +export function CoderouterApiSandboxFileStatPost200ResponseToJSON( + json: any, +): CoderouterApiSandboxFileStatPost200Response { + return CoderouterApiSandboxFileStatPost200ResponseToJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileStatPost200ResponseToJSONTyped( + value?: CoderouterApiSandboxFileStatPost200Response | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + type: value['type'], + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileStatPostRequest.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileStatPostRequest.ts new file mode 100644 index 0000000000..0d10810907 --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileStatPostRequest.ts @@ -0,0 +1,75 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface CoderouterApiSandboxFileStatPostRequest + */ +export interface CoderouterApiSandboxFileStatPostRequest { + /** + * The path of the file to stat. + * @type {string} + * @memberof CoderouterApiSandboxFileStatPostRequest + */ + path: string; +} + +/** + * Check if a given object implements the CoderouterApiSandboxFileStatPostRequest interface. + */ +export function instanceOfCoderouterApiSandboxFileStatPostRequest( + value: object, +): value is CoderouterApiSandboxFileStatPostRequest { + if (!('path' in value) || value['path'] === undefined) return false; + return true; +} + +export function CoderouterApiSandboxFileStatPostRequestFromJSON( + json: any, +): CoderouterApiSandboxFileStatPostRequest { + return CoderouterApiSandboxFileStatPostRequestFromJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileStatPostRequestFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiSandboxFileStatPostRequest { + if (json == null) { + return json; + } + return { + path: json['path'], + }; +} + +export function CoderouterApiSandboxFileStatPostRequestToJSON( + json: any, +): CoderouterApiSandboxFileStatPostRequest { + return CoderouterApiSandboxFileStatPostRequestToJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileStatPostRequestToJSONTyped( + value?: CoderouterApiSandboxFileStatPostRequest | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + path: value['path'], + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileWritePostRequest.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileWritePostRequest.ts new file mode 100644 index 0000000000..b8d3fd64fd --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileWritePostRequest.ts @@ -0,0 +1,87 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +import type { CoderouterApiSandboxFileWritePostRequestFilesInner } from './CoderouterApiSandboxFileWritePostRequestFilesInner'; +import { + CoderouterApiSandboxFileWritePostRequestFilesInnerFromJSON, + CoderouterApiSandboxFileWritePostRequestFilesInnerFromJSONTyped, + CoderouterApiSandboxFileWritePostRequestFilesInnerToJSON, + CoderouterApiSandboxFileWritePostRequestFilesInnerToJSONTyped, +} from './CoderouterApiSandboxFileWritePostRequestFilesInner'; + +/** + * + * @export + * @interface CoderouterApiSandboxFileWritePostRequest + */ +export interface CoderouterApiSandboxFileWritePostRequest { + /** + * + * @type {Array} + * @memberof CoderouterApiSandboxFileWritePostRequest + */ + files: Array; +} + +/** + * Check if a given object implements the CoderouterApiSandboxFileWritePostRequest interface. + */ +export function instanceOfCoderouterApiSandboxFileWritePostRequest( + value: object, +): value is CoderouterApiSandboxFileWritePostRequest { + if (!('files' in value) || value['files'] === undefined) return false; + return true; +} + +export function CoderouterApiSandboxFileWritePostRequestFromJSON( + json: any, +): CoderouterApiSandboxFileWritePostRequest { + return CoderouterApiSandboxFileWritePostRequestFromJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileWritePostRequestFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiSandboxFileWritePostRequest { + if (json == null) { + return json; + } + return { + files: (json['files'] as Array).map( + CoderouterApiSandboxFileWritePostRequestFilesInnerFromJSON, + ), + }; +} + +export function CoderouterApiSandboxFileWritePostRequestToJSON( + json: any, +): CoderouterApiSandboxFileWritePostRequest { + return CoderouterApiSandboxFileWritePostRequestToJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileWritePostRequestToJSONTyped( + value?: CoderouterApiSandboxFileWritePostRequest | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + files: (value['files'] as Array).map( + CoderouterApiSandboxFileWritePostRequestFilesInnerToJSON, + ), + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileWritePostRequestFilesInner.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileWritePostRequestFilesInner.ts new file mode 100644 index 0000000000..92c3da1d6c --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileWritePostRequestFilesInner.ts @@ -0,0 +1,93 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface CoderouterApiSandboxFileWritePostRequestFilesInner + */ +export interface CoderouterApiSandboxFileWritePostRequestFilesInner { + /** + * The path of the file to write. + * @type {string} + * @memberof CoderouterApiSandboxFileWritePostRequestFilesInner + */ + path: string; + /** + * The content of the file to write. + * @type {string} + * @memberof CoderouterApiSandboxFileWritePostRequestFilesInner + */ + data: string; + /** + * Whether to overwrite the file if it already exists. + * @type {boolean} + * @memberof CoderouterApiSandboxFileWritePostRequestFilesInner + */ + overwrite: boolean; +} + +/** + * Check if a given object implements the CoderouterApiSandboxFileWritePostRequestFilesInner interface. + */ +export function instanceOfCoderouterApiSandboxFileWritePostRequestFilesInner( + value: object, +): value is CoderouterApiSandboxFileWritePostRequestFilesInner { + if (!('path' in value) || value['path'] === undefined) return false; + if (!('data' in value) || value['data'] === undefined) return false; + if (!('overwrite' in value) || value['overwrite'] === undefined) return false; + return true; +} + +export function CoderouterApiSandboxFileWritePostRequestFilesInnerFromJSON( + json: any, +): CoderouterApiSandboxFileWritePostRequestFilesInner { + return CoderouterApiSandboxFileWritePostRequestFilesInnerFromJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileWritePostRequestFilesInnerFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiSandboxFileWritePostRequestFilesInner { + if (json == null) { + return json; + } + return { + path: json['path'], + data: json['data'], + overwrite: json['overwrite'], + }; +} + +export function CoderouterApiSandboxFileWritePostRequestFilesInnerToJSON( + json: any, +): CoderouterApiSandboxFileWritePostRequestFilesInner { + return CoderouterApiSandboxFileWritePostRequestFilesInnerToJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileWritePostRequestFilesInnerToJSONTyped( + value?: CoderouterApiSandboxFileWritePostRequestFilesInner | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + path: value['path'], + data: value['data'], + overwrite: value['overwrite'], + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxUrlPost200Response.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxUrlPost200Response.ts new file mode 100644 index 0000000000..75092dca8c --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxUrlPost200Response.ts @@ -0,0 +1,75 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface CoderouterApiSandboxUrlPost200Response + */ +export interface CoderouterApiSandboxUrlPost200Response { + /** + * The URL of the sandbox. + * @type {string} + * @memberof CoderouterApiSandboxUrlPost200Response + */ + url: string; +} + +/** + * Check if a given object implements the CoderouterApiSandboxUrlPost200Response interface. + */ +export function instanceOfCoderouterApiSandboxUrlPost200Response( + value: object, +): value is CoderouterApiSandboxUrlPost200Response { + if (!('url' in value) || value['url'] === undefined) return false; + return true; +} + +export function CoderouterApiSandboxUrlPost200ResponseFromJSON( + json: any, +): CoderouterApiSandboxUrlPost200Response { + return CoderouterApiSandboxUrlPost200ResponseFromJSONTyped(json, false); +} + +export function CoderouterApiSandboxUrlPost200ResponseFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiSandboxUrlPost200Response { + if (json == null) { + return json; + } + return { + url: json['url'], + }; +} + +export function CoderouterApiSandboxUrlPost200ResponseToJSON( + json: any, +): CoderouterApiSandboxUrlPost200Response { + return CoderouterApiSandboxUrlPost200ResponseToJSONTyped(json, false); +} + +export function CoderouterApiSandboxUrlPost200ResponseToJSONTyped( + value?: CoderouterApiSandboxUrlPost200Response | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + url: value['url'], + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/index.ts b/packages/code-provider/src/providers/coderouter/codegen/models/index.ts new file mode 100644 index 0000000000..00b5eec595 --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/index.ts @@ -0,0 +1,21 @@ +/* tslint:disable */ +/* eslint-disable */ +export * from './CoderouterApiAuthSignPost200Response'; +export * from './CoderouterApiAuthSignPost401Response'; +export * from './CoderouterApiAuthSignPostRequest'; +export * from './CoderouterApiSandboxCreatePost200Response'; +export * from './CoderouterApiSandboxCreatePostRequest'; +export * from './CoderouterApiSandboxFileCopyPostRequest'; +export * from './CoderouterApiSandboxFileDeletePostRequest'; +export * from './CoderouterApiSandboxFileDownloadPostRequest'; +export * from './CoderouterApiSandboxFileListPost200Response'; +export * from './CoderouterApiSandboxFileListPost200ResponseFilesInner'; +export * from './CoderouterApiSandboxFileListPostRequest'; +export * from './CoderouterApiSandboxFileReadPost200Response'; +export * from './CoderouterApiSandboxFileReadPostRequest'; +export * from './CoderouterApiSandboxFileRenamePostRequest'; +export * from './CoderouterApiSandboxFileStatPost200Response'; +export * from './CoderouterApiSandboxFileStatPostRequest'; +export * from './CoderouterApiSandboxFileWritePostRequest'; +export * from './CoderouterApiSandboxFileWritePostRequestFilesInner'; +export * from './CoderouterApiSandboxUrlPost200Response'; diff --git a/packages/code-provider/src/providers/coderouter/codegen/runtime.ts b/packages/code-provider/src/providers/coderouter/codegen/runtime.ts new file mode 100644 index 0000000000..bd9d3ba746 --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/runtime.ts @@ -0,0 +1,497 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +export const BASE_PATH = 'http://localhost'.replace(/\/+$/, ''); + +export interface ConfigurationParameters { + basePath?: string; // override base path + fetchApi?: FetchAPI; // override for fetch implementation + middleware?: Middleware[]; // middleware to apply before/after fetch requests + queryParamsStringify?: (params: HTTPQuery) => string; // stringify function for query strings + username?: string; // parameter for basic security + password?: string; // parameter for basic security + apiKey?: string | Promise | ((name: string) => string | Promise); // parameter for apiKey security + accessToken?: + | string + | Promise + | ((name?: string, scopes?: string[]) => string | Promise); // parameter for oauth2 security + headers?: HTTPHeaders; //header params we want to use on every request + credentials?: RequestCredentials; //value for the credentials param we want to use on each request +} + +export class Configuration { + constructor(private configuration: ConfigurationParameters = {}) {} + + set config(configuration: Configuration) { + this.configuration = configuration; + } + + get basePath(): string { + return this.configuration.basePath != null ? this.configuration.basePath : BASE_PATH; + } + + get fetchApi(): FetchAPI | undefined { + return this.configuration.fetchApi; + } + + get middleware(): Middleware[] { + return this.configuration.middleware || []; + } + + get queryParamsStringify(): (params: HTTPQuery) => string { + return this.configuration.queryParamsStringify || querystring; + } + + get username(): string | undefined { + return this.configuration.username; + } + + get password(): string | undefined { + return this.configuration.password; + } + + get apiKey(): ((name: string) => string | Promise) | undefined { + const apiKey = this.configuration.apiKey; + if (apiKey) { + return typeof apiKey === 'function' ? apiKey : () => apiKey; + } + return undefined; + } + + get accessToken(): + | ((name?: string, scopes?: string[]) => string | Promise) + | undefined { + const accessToken = this.configuration.accessToken; + if (accessToken) { + return typeof accessToken === 'function' ? accessToken : async () => accessToken; + } + return undefined; + } + + get headers(): HTTPHeaders | undefined { + return this.configuration.headers; + } + + get credentials(): RequestCredentials | undefined { + return this.configuration.credentials; + } +} + +export const DefaultConfig = new Configuration(); + +/** + * This is the base class for all generated API classes. + */ +export class BaseAPI { + private static readonly jsonRegex = new RegExp( + '^(:?application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(:?;.*)?$', + 'i', + ); + private middleware: Middleware[]; + + constructor(protected configuration = DefaultConfig) { + this.middleware = configuration.middleware; + } + + withMiddleware(this: T, ...middlewares: Middleware[]) { + const next = this.clone(); + next.middleware = next.middleware.concat(...middlewares); + return next; + } + + withPreMiddleware(this: T, ...preMiddlewares: Array) { + const middlewares = preMiddlewares.map((pre) => ({ pre })); + return this.withMiddleware(...middlewares); + } + + withPostMiddleware(this: T, ...postMiddlewares: Array) { + const middlewares = postMiddlewares.map((post) => ({ post })); + return this.withMiddleware(...middlewares); + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * @param mime - MIME (Multipurpose Internet Mail Extensions) + * @return True if the given MIME is JSON, false otherwise. + */ + protected isJsonMime(mime: string | null | undefined): boolean { + if (!mime) { + return false; + } + return BaseAPI.jsonRegex.test(mime); + } + + protected async request( + context: RequestOpts, + initOverrides?: RequestInit | InitOverrideFunction, + ): Promise { + const { url, init } = await this.createFetchParams(context, initOverrides); + const response = await this.fetchApi(url, init); + if (response && response.status >= 200 && response.status < 300) { + return response; + } + throw new ResponseError(response, 'Response returned an error code'); + } + + private async createFetchParams( + context: RequestOpts, + initOverrides?: RequestInit | InitOverrideFunction, + ) { + let url = this.configuration.basePath + context.path; + if (context.query !== undefined && Object.keys(context.query).length !== 0) { + // only add the querystring to the URL if there are query parameters. + // this is done to avoid urls ending with a "?" character which buggy webservers + // do not handle correctly sometimes. + url += '?' + this.configuration.queryParamsStringify(context.query); + } + + const headers = Object.assign({}, this.configuration.headers, context.headers); + Object.keys(headers).forEach((key) => + headers[key] === undefined ? delete headers[key] : {}, + ); + + const initOverrideFn = + typeof initOverrides === 'function' ? initOverrides : async () => initOverrides; + + const initParams = { + method: context.method, + headers, + body: context.body, + credentials: this.configuration.credentials, + }; + + const overriddenInit: RequestInit = { + ...initParams, + ...(await initOverrideFn({ + init: initParams, + context, + })), + }; + + let body: any; + if ( + isFormData(overriddenInit.body) || + overriddenInit.body instanceof URLSearchParams || + isBlob(overriddenInit.body) + ) { + body = overriddenInit.body; + } else if (this.isJsonMime(headers['Content-Type'])) { + body = JSON.stringify(overriddenInit.body); + } else { + body = overriddenInit.body; + } + + const init: RequestInit = { + ...overriddenInit, + body, + }; + + return { url, init }; + } + + private fetchApi = async (url: string, init: RequestInit) => { + let fetchParams = { url, init }; + for (const middleware of this.middleware) { + if (middleware.pre) { + fetchParams = + (await middleware.pre({ + fetch: this.fetchApi, + ...fetchParams, + })) || fetchParams; + } + } + let response: Response | undefined = undefined; + try { + response = await (this.configuration.fetchApi || fetch)( + fetchParams.url, + fetchParams.init, + ); + } catch (e) { + for (const middleware of this.middleware) { + if (middleware.onError) { + response = + (await middleware.onError({ + fetch: this.fetchApi, + url: fetchParams.url, + init: fetchParams.init, + error: e, + response: response ? response.clone() : undefined, + })) || response; + } + } + if (response === undefined) { + if (e instanceof Error) { + throw new FetchError( + e, + 'The request failed and the interceptors did not return an alternative response', + ); + } else { + throw e; + } + } + } + for (const middleware of this.middleware) { + if (middleware.post) { + response = + (await middleware.post({ + fetch: this.fetchApi, + url: fetchParams.url, + init: fetchParams.init, + response: response.clone(), + })) || response; + } + } + return response; + }; + + /** + * Create a shallow clone of `this` by constructing a new instance + * and then shallow cloning data members. + */ + private clone(this: T): T { + const constructor = this.constructor as any; + const next = new constructor(this.configuration); + next.middleware = this.middleware.slice(); + return next; + } +} + +function isBlob(value: any): value is Blob { + return typeof Blob !== 'undefined' && value instanceof Blob; +} + +function isFormData(value: any): value is FormData { + return typeof FormData !== 'undefined' && value instanceof FormData; +} + +export class ResponseError extends Error { + override name: 'ResponseError' = 'ResponseError'; + constructor( + public response: Response, + msg?: string, + ) { + super(msg); + } +} + +export class FetchError extends Error { + override name: 'FetchError' = 'FetchError'; + constructor( + public cause: Error, + msg?: string, + ) { + super(msg); + } +} + +export class RequiredError extends Error { + override name: 'RequiredError' = 'RequiredError'; + constructor( + public field: string, + msg?: string, + ) { + super(msg); + } +} + +export const COLLECTION_FORMATS = { + csv: ',', + ssv: ' ', + tsv: '\t', + pipes: '|', +}; + +export type FetchAPI = WindowOrWorkerGlobalScope['fetch']; + +export type Json = any; +export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; +export type HTTPHeaders = { [key: string]: string }; +export type HTTPQuery = { + [key: string]: + | string + | number + | null + | boolean + | Array + | Set + | HTTPQuery; +}; +export type HTTPBody = Json | FormData | URLSearchParams; +export type HTTPRequestInit = { + headers?: HTTPHeaders; + method: HTTPMethod; + credentials?: RequestCredentials; + body?: HTTPBody; +}; +export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; + +export type InitOverrideFunction = (requestContext: { + init: HTTPRequestInit; + context: RequestOpts; +}) => Promise; + +export interface FetchParams { + url: string; + init: RequestInit; +} + +export interface RequestOpts { + path: string; + method: HTTPMethod; + headers: HTTPHeaders; + query?: HTTPQuery; + body?: HTTPBody; +} + +export function querystring(params: HTTPQuery, prefix: string = ''): string { + return Object.keys(params) + .map((key) => querystringSingleKey(key, params[key], prefix)) + .filter((part) => part.length > 0) + .join('&'); +} + +function querystringSingleKey( + key: string, + value: + | string + | number + | null + | undefined + | boolean + | Array + | Set + | HTTPQuery, + keyPrefix: string = '', +): string { + const fullKey = keyPrefix + (keyPrefix.length ? `[${key}]` : key); + if (value instanceof Array) { + const multiValue = value + .map((singleValue) => encodeURIComponent(String(singleValue))) + .join(`&${encodeURIComponent(fullKey)}=`); + return `${encodeURIComponent(fullKey)}=${multiValue}`; + } + if (value instanceof Set) { + const valueAsArray = Array.from(value); + return querystringSingleKey(key, valueAsArray, keyPrefix); + } + if (value instanceof Date) { + return `${encodeURIComponent(fullKey)}=${encodeURIComponent(value.toISOString())}`; + } + if (value instanceof Object) { + return querystring(value as HTTPQuery, fullKey); + } + return `${encodeURIComponent(fullKey)}=${encodeURIComponent(String(value))}`; +} + +export function exists(json: any, key: string) { + const value = json[key]; + return value !== null && value !== undefined; +} + +export function mapValues(data: any, fn: (item: any) => any) { + const result: { [key: string]: any } = {}; + for (const key of Object.keys(data)) { + result[key] = fn(data[key]); + } + return result; +} + +export function canConsumeForm(consumes: Consume[]): boolean { + for (const consume of consumes) { + if ('multipart/form-data' === consume.contentType) { + return true; + } + } + return false; +} + +export interface Consume { + contentType: string; +} + +export interface RequestContext { + fetch: FetchAPI; + url: string; + init: RequestInit; +} + +export interface ResponseContext { + fetch: FetchAPI; + url: string; + init: RequestInit; + response: Response; +} + +export interface ErrorContext { + fetch: FetchAPI; + url: string; + init: RequestInit; + error: unknown; + response?: Response; +} + +export interface Middleware { + pre?(context: RequestContext): Promise; + post?(context: ResponseContext): Promise; + onError?(context: ErrorContext): Promise; +} + +export interface ApiResponse { + raw: Response; + value(): Promise; +} + +export interface ResponseTransformer { + (json: any): T; +} + +export class JSONApiResponse { + constructor( + public raw: Response, + private transformer: ResponseTransformer = (jsonValue: any) => jsonValue, + ) {} + + async value(): Promise { + return this.transformer(await this.raw.json()); + } +} + +export class VoidApiResponse { + constructor(public raw: Response) {} + + async value(): Promise { + return undefined; + } +} + +export class BlobApiResponse { + constructor(public raw: Response) {} + + async value(): Promise { + return await this.raw.blob(); + } +} + +export class TextApiResponse { + constructor(public raw: Response) {} + + async value(): Promise { + return await this.raw.text(); + } +} diff --git a/packages/code-provider/src/providers/coderouter/index.ts b/packages/code-provider/src/providers/coderouter/index.ts new file mode 100644 index 0000000000..7862bbc4de --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/index.ts @@ -0,0 +1,485 @@ +import { + Provider, + ProviderBackgroundCommand, + ProviderFileWatcher, + ProviderTask, + ProviderTerminal, + type CopyFileOutput, + type CopyFilesInput, + type CreateProjectInput, + type CreateProjectOutput, + type CreateSessionInput, + type CreateSessionOutput, + type CreateTerminalInput, + type CreateTerminalOutput, + type DeleteFilesInput, + type DeleteFilesOutput, + type DownloadFilesInput, + type DownloadFilesOutput, + type GetProjectUrlInput, + type GetProjectUrlOutput, + type GetTaskInput, + type GetTaskOutput, + type GitStatusInput, + type GitStatusOutput, + type InitializeInput, + type InitializeOutput, + type ListFilesInput, + type ListFilesOutput, + type ListProjectsInput, + type ListProjectsOutput, + type PauseProjectInput, + type PauseProjectOutput, + type ReadFileInput, + type ReadFileOutput, + type RenameFileInput, + type RenameFileOutput, + type SetupInput, + type SetupOutput, + type StatFileInput, + type StatFileOutput, + type StopProjectInput, + type StopProjectOutput, + type TerminalBackgroundCommandInput, + type TerminalBackgroundCommandOutput, + type TerminalCommandInput, + type TerminalCommandOutput, + type WatchEvent, + type WatchFilesInput, + type WatchFilesOutput, + type WriteFileInput, + type WriteFileOutput, +} from '../../types'; + +import * as OpenAPI from './codegen'; + +export interface CoderouterProviderOptions { + // URL to Coderouter + url?: string; + // Onlook's sandbox ID + sandboxId?: string; + // Onlook's user ID + userId?: string; + getSession?: ( + provider: CoderouterProvider, + sandboxId: string, + userId?: string, + ) => Promise<{ jwt?: string }>; +} + +export class CoderouterProvider extends Provider { + private readonly options: CoderouterProviderOptions; + private jwt: string | null = null; + + private readonly api: OpenAPI.DefaultApi; + + constructor(options: CoderouterProviderOptions) { + super(); + this.options = options; + + const configurationParameters: OpenAPI.ConfigurationParameters = { + basePath: this.options.url || 'https://onlook.internal', + }; + const configuration = new OpenAPI.Configuration(configurationParameters); + + // Use configuration with your_api + this.api = new OpenAPI.DefaultApi(configuration); + } + + async initialize(input: InitializeInput): Promise { + if (!this.options.sandboxId) { + return {}; + } + if (this.options.getSession) { + const res = await this.options.getSession( + this, + this.options.sandboxId, + this.options.userId, + ); + this.jwt = res.jwt ?? null; + + if (typeof window !== 'undefined') { + const url = await this.getProjectUrl({ args: {} }); + console.log('----> url', url); + } + } + return {}; + } + + async writeFile(input: WriteFileInput): Promise { + const res = await this.api.coderouterApiSandboxFileWritePost( + { + coderouterApiSandboxFileWritePostRequest: { + files: [ + { + path: input.args.path, + data: + input.args.content instanceof Uint8Array + ? Buffer.from(input.args.content).toString('utf-8') + : input.args.content, + overwrite: input.args.overwrite ?? true, + }, + ], + }, + }, + this.requestInitOverrides(), + ); + return { + success: true, + }; + } + + async renameFile(input: RenameFileInput): Promise { + await this.api.coderouterApiSandboxFileRenamePost( + { + coderouterApiSandboxFileRenamePostRequest: { + oldPath: input.args.oldPath, + newPath: input.args.newPath, + }, + }, + this.requestInitOverrides(), + ); + return {}; + } + + async statFile(input: StatFileInput): Promise { + const res = await this.api.coderouterApiSandboxFileStatPost( + { + coderouterApiSandboxFileStatPostRequest: { + path: input.args.path, + }, + }, + this.requestInitOverrides(), + ); + return { + type: res.type, + }; + } + + async deleteFiles(input: DeleteFilesInput): Promise { + await this.api.coderouterApiSandboxFileDeletePost( + { + coderouterApiSandboxFileDeletePostRequest: { + path: input.args.path, + }, + }, + this.requestInitOverrides(), + ); + return {}; + } + + async listFiles(input: ListFilesInput): Promise { + const res = await this.api + .coderouterApiSandboxFileListPost( + { + coderouterApiSandboxFileListPostRequest: { + path: input.args.path, + }, + }, + this.requestInitOverrides(), + ) + .catch((error: any) => { + if (error?.response?.status === 404) { + return { + files: [], + }; + } + throw error; + }); + return { + files: res.files.map((file) => ({ + name: file.name, + path: file.path, + type: file.type, + isSymlink: false, + })), + }; + } + + async readFile(input: ReadFileInput): Promise { + try { + const res = await this.api.coderouterApiSandboxFileReadPost( + { + coderouterApiSandboxFileReadPostRequest: { + path: input.args.path, + }, + }, + this.requestInitOverrides(), + ); + return { + file: { + path: input.args.path, + content: res.data, + type: 'text', + toString: () => { + return res.data; + }, + }, + }; + } catch (error: any) { + if (error?.response?.status === 404) { + return { + file: null, + }; + } + throw error; + } + } + + async downloadFiles(input: DownloadFilesInput): Promise { + const res: any = await this.api.coderouterApiSandboxFileDownloadPost( + { + coderouterApiSandboxFileDownloadPostRequest: { + path: input.args.path, + }, + }, + this.requestInitOverrides(), + ); + return { + url: res.url, + }; + } + + async copyFiles(input: CopyFilesInput): Promise { + await this.api.coderouterApiSandboxFileCopyPost( + { + coderouterApiSandboxFileCopyPostRequest: { + source: input.args.sourcePath, + destination: input.args.targetPath, + recursive: input.args.recursive ?? true, + overwrite: input.args.overwrite ?? true, + }, + }, + this.requestInitOverrides(), + ); + return {}; + } + + async watchFiles(input: WatchFilesInput): Promise { + return { + watcher: new CoderouterFileWatcher(), + }; + } + + async createTerminal(input: CreateTerminalInput): Promise { + return { + terminal: new CoderouterTerminal(), + }; + } + + async getTask(input: GetTaskInput): Promise { + return { + task: new CoderouterTask(), + }; + } + + async runCommand(input: TerminalCommandInput): Promise { + return { + output: '', + }; + } + + async runBackgroundCommand( + input: TerminalBackgroundCommandInput, + ): Promise { + return { + command: new CoderouterCommand(), + }; + } + + async gitStatus(input: GitStatusInput): Promise { + return { + changedFiles: [], + }; + } + + async setup(input: SetupInput): Promise { + return {}; + } + + async createSession(input: CreateSessionInput): Promise { + // called in the backend + const res = await this.api.coderouterApiAuthSignPost( + { + coderouterApiAuthSignPostRequest: { + sandboxId: this.options.sandboxId, + userId: this.options.userId, + }, + }, + { + headers: { + 'content-type': 'application/json', + authorization: `Bearer ${process?.env?.SUPAROUTA_API_KEY}`, + }, + }, + ); + const { jwt } = res; + return { + jwt, + }; + } + + async reload(): Promise { + // TODO: Implement + return true; + } + + async reconnect(): Promise { + // TODO: Implement + } + + async ping(): Promise { + return true; + } + + async createProject(input: CreateProjectInput): Promise { + const res = await this.api.coderouterApiSandboxCreatePost( + { + coderouterApiSandboxCreatePostRequest: { + templateId: input.templateId ?? 'default', + metadata: {}, + }, + }, + this.requestInitOverrides(), + ); + return { + id: input.id, + }; + } + + async pauseProject(input: PauseProjectInput): Promise { + return {}; + } + + async stopProject(input: StopProjectInput): Promise { + return {}; + } + + async listProjects(input: ListProjectsInput): Promise { + return { projects: [] }; + } + + async getProjectUrl(input: GetProjectUrlInput): Promise { + const res = await this.api.coderouterApiSandboxUrlPost(this.requestInitOverrides()); + return { + url: res.url, + }; + } + + async destroy(): Promise { + // TODO: Implement + } + + private requestInitOverrides() { + return { + headers: { + 'content-type': 'application/json', + 'X-Auth-Jwt': this.jwt ?? '', + }, + }; + } +} + +export class CoderouterFileWatcher extends ProviderFileWatcher { + start(input: WatchFilesInput): Promise { + return Promise.resolve(); + } + + stop(): Promise { + return Promise.resolve(); + } + + registerEventCallback(callback: (event: WatchEvent) => Promise): void { + // TODO: Implement + } +} + +export class CoderouterTerminal extends ProviderTerminal { + get id(): string { + return 'unimplemented'; + } + + get name(): string { + return 'unimplemented'; + } + + open(): Promise { + return Promise.resolve(''); + } + + write(): Promise { + return Promise.resolve(); + } + + run(): Promise { + return Promise.resolve(); + } + + kill(): Promise { + return Promise.resolve(); + } + + onOutput(callback: (data: string) => void): () => void { + return () => {}; + } +} + +export class CoderouterTask extends ProviderTask { + get id(): string { + return 'unimplemented'; + } + + get name(): string { + return 'unimplemented'; + } + + get command(): string { + return 'unimplemented'; + } + + open(): Promise { + return Promise.resolve(''); + } + + run(): Promise { + return Promise.resolve(); + } + + restart(): Promise { + return Promise.resolve(); + } + + stop(): Promise { + return Promise.resolve(); + } + + onOutput(callback: (data: string) => void): () => void { + return () => {}; + } +} + +export class CoderouterCommand extends ProviderBackgroundCommand { + get name(): string { + return 'unimplemented'; + } + + get command(): string { + return 'unimplemented'; + } + + open(): Promise { + return Promise.resolve(''); + } + + restart(): Promise { + return Promise.resolve(); + } + + kill(): Promise { + return Promise.resolve(); + } + + onOutput(callback: (data: string) => void): () => void { + return () => {}; + } +} diff --git a/packages/code-provider/src/providers/codesandbox/index.ts b/packages/code-provider/src/providers/codesandbox/index.ts index 11cdeb43be..f253e8ffd7 100644 --- a/packages/code-provider/src/providers/codesandbox/index.ts +++ b/packages/code-provider/src/providers/codesandbox/index.ts @@ -27,6 +27,8 @@ import { type DeleteFilesOutput, type DownloadFilesInput, type DownloadFilesOutput, + type GetProjectUrlInput, + type GetProjectUrlOutput, type GetTaskInput, type GetTaskOutput, type GitStatusInput, @@ -194,7 +196,7 @@ export class CodesandboxProvider extends Provider { return { projects: projects.sandboxes.map((project) => ({ id: project.id, - name: project.title, + title: project.title, description: project.description, createdAt: project.createdAt, updatedAt: project.updatedAt, @@ -204,6 +206,12 @@ export class CodesandboxProvider extends Provider { return { projects: [] }; } + async getProjectUrl(input: GetProjectUrlInput): Promise { + return { + url: '', + }; + } + async writeFile(input: WriteFileInput): Promise { if (!this.client) { throw new Error('Client not initialized'); diff --git a/packages/code-provider/src/providers/nodefs/index.ts b/packages/code-provider/src/providers/nodefs/index.ts index ac45a15ffa..fbfce284fb 100644 --- a/packages/code-provider/src/providers/nodefs/index.ts +++ b/packages/code-provider/src/providers/nodefs/index.ts @@ -16,6 +16,8 @@ import { type DeleteFilesOutput, type DownloadFilesInput, type DownloadFilesOutput, + type GetProjectUrlInput, + type GetProjectUrlOutput, type GetTaskInput, type GetTaskOutput, type GitStatusInput, @@ -186,7 +188,13 @@ export class NodeFsProvider extends Provider { } async listProjects(input: ListProjectsInput): Promise { - return {}; + return { projects: [] }; + } + + async getProjectUrl(input: GetProjectUrlInput): Promise { + return { + url: '', + }; } async destroy(): Promise { diff --git a/packages/code-provider/src/types.ts b/packages/code-provider/src/types.ts index 7dcff3f050..e2ff69cbec 100644 --- a/packages/code-provider/src/types.ts +++ b/packages/code-provider/src/types.ts @@ -48,6 +48,7 @@ export interface ListFilesInput { } export interface ListFilesOutputFile { name: string; + path: string; type: 'file' | 'directory'; isSymlink: boolean; } @@ -62,7 +63,7 @@ export interface ReadFileInput { } export type ReadFileOutputFile = SandboxFile & { toString: () => string }; export interface ReadFileOutput { - file: ReadFileOutputFile; + file: ReadFileOutputFile | null; } export interface DeleteFilesInput { @@ -130,6 +131,7 @@ export interface TerminalCommandInput { } export interface TerminalCommandOutput { output: string; + error?: string; } export interface TerminalBackgroundCommandInput { @@ -156,6 +158,8 @@ export interface SetupOutput {} export interface CreateProjectInput { source: string; id: string; + userId?: string; + templateId?: string; title?: string; description?: string; tags?: string[]; @@ -171,14 +175,31 @@ export interface StopProjectInput {} export interface StopProjectOutput {} export interface ListProjectsInput {} -export interface ListProjectsOutput {} +export interface ListProjectsOutput { + projects: Array<{ + id: string; + title?: string; + description?: string; + createdAt?: Date; + updatedAt?: Date; + }>; +} + +export interface GetProjectUrlInput { + args: {}; +} +export interface GetProjectUrlOutput { + url: string; +} export interface CreateSessionInput { args: { id: string; }; } -export interface CreateSessionOutput {} +export interface CreateSessionOutput { + jwt?: string; +} export abstract class Provider { abstract writeFile(input: WriteFileInput): Promise; @@ -221,6 +242,7 @@ export abstract class Provider { abstract pauseProject(input: PauseProjectInput): Promise; abstract stopProject(input: StopProjectInput): Promise; abstract listProjects(input: ListProjectsInput): Promise; + abstract getProjectUrl(input: GetProjectUrlInput): Promise; // this is called when the provider instance is no longer needed abstract destroy(): Promise; diff --git a/packages/scripts/src/api-keys.ts b/packages/scripts/src/api-keys.ts index fcffbe286b..4ad12c336c 100644 --- a/packages/scripts/src/api-keys.ts +++ b/packages/scripts/src/api-keys.ts @@ -12,10 +12,16 @@ interface ApiKeyConfig { const API_KEYS: Record = { CSB_API_KEY: { name: 'CSB_API_KEY', - message: 'Enter your Codesandbox API key:', - required: true, + message: 'Enter your Codesandbox API key (optional, leave blank if you are using E2B):', + required: false, description: 'Codesandbox', }, + SUPAROUTA_API_KEY: { + name: 'SUPAROUTA_API_KEY', + message: 'Enter your Coderouter API key:', + required: true, + description: 'Coderouter', + }, ANTHROPIC_API_KEY: { name: 'ANTHROPIC_API_KEY', message: 'Enter your Anthropic API key:', From 6b6b3faf13f59c01b0e6d86263b6d92850b3019d Mon Sep 17 00:00:00 2001 From: Thomas Potaire Date: Wed, 27 Aug 2025 12:18:50 -0700 Subject: [PATCH 2/5] committing file watcher code --- .../src/api/sandbox/file/watch/index.test.ts | 18 + .../src/api/sandbox/file/watch/index.ts | 63 +++ .../provider/definition/sandbox/file/index.ts | 75 +-- .../src/provider/e2b/sandbox/file/index.ts | 521 +++++++++++------- apps/coderouter/src/server.ts | 2 + .../components/store/editor/sandbox/index.ts | 2 +- .../codegen/.openapi-generator/FILES | 4 +- .../coderouter/codegen/apis/DefaultApi.ts | 64 +++ ...outerApiSandboxFileWatchPost200Response.ts | 87 +++ ...dboxFileWatchPost200ResponseEventsInner.ts | 95 ++++ ...oderouterApiSandboxFileWatchPostRequest.ts | 93 ++++ .../coderouter/codegen/models/index.ts | 3 + .../src/providers/coderouter/index.ts | 101 +++- .../src/providers/codesandbox/utils/utils.ts | 2 - 14 files changed, 895 insertions(+), 235 deletions(-) create mode 100644 apps/coderouter/src/api/sandbox/file/watch/index.test.ts create mode 100644 apps/coderouter/src/api/sandbox/file/watch/index.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileWatchPost200Response.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileWatchPost200ResponseEventsInner.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileWatchPostRequest.ts diff --git a/apps/coderouter/src/api/sandbox/file/watch/index.test.ts b/apps/coderouter/src/api/sandbox/file/watch/index.test.ts new file mode 100644 index 0000000000..6336c72869 --- /dev/null +++ b/apps/coderouter/src/api/sandbox/file/watch/index.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'bun:test'; +import { Hono } from 'hono'; +import { api_sandbox_file_write } from './index'; + +describe('sandbox files write endpoints', () => { + it('POST /coderouter/api/sandbox/file/write returns empty object', async () => { + const app = new Hono(); + api_sandbox_file_write(app); + + const response = await app.request(env.URL_PATH_PREFIX + '/api/sandbox/file/write', { + method: 'POST', + }); + + expect(response.status).toBe(200); + const body = await response.json(); + expect(body).toEqual({}); + }); +}); diff --git a/apps/coderouter/src/api/sandbox/file/watch/index.ts b/apps/coderouter/src/api/sandbox/file/watch/index.ts new file mode 100644 index 0000000000..1c9f70c1b3 --- /dev/null +++ b/apps/coderouter/src/api/sandbox/file/watch/index.ts @@ -0,0 +1,63 @@ +import { SandboxFileWatchInput, SandboxFileWatchOutput } from '@/provider/definition/sandbox/file'; +import { LocalHono } from '@/server'; +import { JwtAuthResponses } from '@/util/auth'; +import { createRoute, z } from '@hono/zod-openapi'; +import { env } from 'bun'; + +const BodySchema: z.ZodType = z.object({ + path: z.string().openapi({ + description: 'The path of the file to write.', + example: '/path/to/file.txt', + }), + recursive: z.boolean().openapi({ + description: 'Whether to watch the file recursively.', + example: false, + }), + excludePaths: z.array(z.string()).openapi({ + description: 'The paths to exclude from the watch.', + example: ['/path/to/exclude'], + }), +}); + +const ResponseSchema: z.ZodType = z.object({ + events: z.array( + z.object({ + path: z.string(), + type: z.enum(['create', 'update', 'delete']), + }), + ), +}); + +const route = createRoute({ + method: 'post', + path: env.URL_PATH_PREFIX + '/api/sandbox/file/watch', + security: [{ jwt: [] }], + request: { + body: { + content: { + 'application/json': { + schema: BodySchema, + }, + }, + }, + }, + responses: { + 200: { + content: { + 'application/json': { + schema: ResponseSchema, + }, + }, + description: 'Watch a file to the sandbox.', + }, + ...JwtAuthResponses, + }, +}); + +export function api_sandbox_file_watch(app: LocalHono) { + app.openapi(route, async (c) => { + const body = await c.req.valid('json'); + const result = await c.get('client').sandbox.file.watch(body); + return c.json(result, 200); + }); +} diff --git a/apps/coderouter/src/provider/definition/sandbox/file/index.ts b/apps/coderouter/src/provider/definition/sandbox/file/index.ts index 525e202281..0297587b73 100644 --- a/apps/coderouter/src/provider/definition/sandbox/file/index.ts +++ b/apps/coderouter/src/provider/definition/sandbox/file/index.ts @@ -1,10 +1,10 @@ -import { Client } from "../../index"; +import { Client } from '../../index'; export interface SandboxFileCopyInput {} export interface SandboxFileCopyOutput {} export interface SandboxFileDeleteInput { - path: string; + path: string; } export interface SandboxFileDeleteOutput {} @@ -12,60 +12,67 @@ export interface SandboxFileDownloadInput {} export interface SandboxFileDownloadOutput {} export interface SandboxFileListInput { - path: string; + path: string; } export interface SandboxFileListOutput { - files: Array<{ - name: string; - path: string; - type: "file" | "directory"; - }>; + files: Array<{ + name: string; + path: string; + type: 'file' | 'directory'; + }>; } export interface SandboxFileReadInput { - path: string; + path: string; } export interface SandboxFileReadOutput { - data: string; + data: string; } export interface SandboxFileRenameInput { - oldPath: string; - newPath: string; + oldPath: string; + newPath: string; } export interface SandboxFileRenameOutput {} export interface SandboxFileStatInput { - path: string; + path: string; } export interface SandboxFileStatOutput { - type: "file" | "directory"; + type: 'file' | 'directory'; } export interface SandboxFileWriteInput { - files: Array<{ - path: string; - data: string; - overwrite: boolean; - }>; + files: Array<{ + path: string; + data: string; + overwrite: boolean; + }>; } export interface SandboxFileWriteOutput {} +export interface SandboxFileWatchInput { + path: string; + recursive: boolean; + excludePaths: string[]; +} +export interface SandboxFileWatchOutput { + events: Array<{ + path: string; + type: 'create' | 'update' | 'delete'; + }>; +} + export abstract class SandboxFile { - constructor(protected readonly client: T) {} + constructor(protected readonly client: T) {} - abstract copy(input: SandboxFileCopyInput): Promise; - abstract delete( - input: SandboxFileDeleteInput - ): Promise; - abstract download( - input: SandboxFileDownloadInput - ): Promise; - abstract list(input: SandboxFileListInput): Promise; - abstract read(input: SandboxFileReadInput): Promise; - abstract rename( - input: SandboxFileRenameInput - ): Promise; - abstract stat(input: SandboxFileStatInput): Promise; - abstract write(input: SandboxFileWriteInput): Promise; + abstract copy(input: SandboxFileCopyInput): Promise; + abstract delete(input: SandboxFileDeleteInput): Promise; + abstract download(input: SandboxFileDownloadInput): Promise; + abstract list(input: SandboxFileListInput): Promise; + abstract read(input: SandboxFileReadInput): Promise; + abstract rename(input: SandboxFileRenameInput): Promise; + abstract stat(input: SandboxFileStatInput): Promise; + abstract watch(input: SandboxFileWatchInput): Promise; + abstract write(input: SandboxFileWriteInput): Promise; } diff --git a/apps/coderouter/src/provider/e2b/sandbox/file/index.ts b/apps/coderouter/src/provider/e2b/sandbox/file/index.ts index c96eee5982..8eb0c343a9 100644 --- a/apps/coderouter/src/provider/e2b/sandbox/file/index.ts +++ b/apps/coderouter/src/provider/e2b/sandbox/file/index.ts @@ -1,203 +1,352 @@ -import { ClientError, ClientErrorCode } from "@/provider/definition"; -import { E2BClient } from "../.."; +import { ClientError, ClientErrorCode } from '@/provider/definition'; +import { E2BClient } from '../..'; import { - SandboxFile, - SandboxFileCopyInput, - SandboxFileCopyOutput, - SandboxFileDeleteInput, - SandboxFileDeleteOutput, - SandboxFileDownloadInput, - SandboxFileDownloadOutput, - SandboxFileListInput, - SandboxFileListOutput, - SandboxFileReadInput, - SandboxFileReadOutput, - SandboxFileRenameInput, - SandboxFileRenameOutput, - SandboxFileStatInput, - SandboxFileStatOutput, - SandboxFileWriteOutput, - SandboxFileWriteInput, -} from "../../../definition/sandbox/file"; -import { NotFoundError } from "@e2b/code-interpreter"; -import path from "path"; + SandboxFile, + SandboxFileCopyInput, + SandboxFileCopyOutput, + SandboxFileDeleteInput, + SandboxFileDeleteOutput, + SandboxFileDownloadInput, + SandboxFileDownloadOutput, + SandboxFileListInput, + SandboxFileListOutput, + SandboxFileReadInput, + SandboxFileReadOutput, + SandboxFileRenameInput, + SandboxFileRenameOutput, + SandboxFileStatInput, + SandboxFileStatOutput, + SandboxFileWriteOutput, + SandboxFileWriteInput, + SandboxFileWatchInput, + SandboxFileWatchOutput, +} from '../../../definition/sandbox/file'; +import { NotFoundError, WatchHandle } from '@e2b/code-interpreter'; +import path from 'path'; + +const watchers: Map = new Map(); export class E2BSandboxFile extends SandboxFile { - // the folder to store the files in the sandbox - // when creating a new template, the code must be stored in this folder - protected folder: string = "/code"; - - constructor(client: E2BClient) { - super(client); - } - - async copy(input: SandboxFileCopyInput): Promise { - throw new ClientError( - ClientErrorCode.Unimplemented, - "Not implemented", - false - ); - } - - async delete( - input: SandboxFileDeleteInput - ): Promise { - if (!this.client._sandbox) { - throw new ClientError( - ClientErrorCode.ImplementationError, - "Sandbox is not instantiated. Call start() or resume() first.", - false - ); + // the folder to store the files in the sandbox + // when creating a new template, the code must be stored in this folder + protected folder: string = '/code'; + + constructor(client: E2BClient) { + super(client); } - await this.client._sandbox.files.remove(this.fullpath(input.path)); - return {}; - } - - async download( - input: SandboxFileDownloadInput - ): Promise { - throw new ClientError( - ClientErrorCode.Unimplemented, - "Not implemented", - false - ); - } - - async list(input: SandboxFileListInput): Promise { - if (!this.client._sandbox) { - throw new ClientError( - ClientErrorCode.ImplementationError, - "Sandbox is not instantiated. Call start() or resume() first.", - false - ); + + async copy(input: SandboxFileCopyInput): Promise { + throw new ClientError(ClientErrorCode.Unimplemented, 'Not implemented', false); } - try { - const p = path.normalize(this.fullpath(input.path)); - const files = await this.client._sandbox.files.list(p); - return { - files: files.map((file) => ({ - name: file.name, - path: file.path.replace(this.folder, ""), - type: file.type === "file" ? "file" : "directory", - })), - }; - } catch (e) { - if (e instanceof NotFoundError) { - throw new ClientError( - ClientErrorCode.FileNotFound, - "Folder or file not found", - false - ); - } - throw e; + + async delete(input: SandboxFileDeleteInput): Promise { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ); + } + await this.client._sandbox.files.remove(this.fullpath(input.path)); + return {}; } - } - - async read(input: SandboxFileReadInput): Promise { - if (!this.client._sandbox) { - throw new ClientError( - ClientErrorCode.ImplementationError, - "Sandbox is not instantiated. Call start() or resume() first.", - false - ); + + async download(input: SandboxFileDownloadInput): Promise { + throw new ClientError(ClientErrorCode.Unimplemented, 'Not implemented', false); } - try { - const data = await this.client._sandbox.files.read( - this.fullpath(input.path) - ); - if (!data) { - throw new ClientError( - ClientErrorCode.FileNotFound, - "File not found", - false - ); - } - - return { - data, - }; - } catch (e) { - if (e instanceof NotFoundError) { - throw new ClientError( - ClientErrorCode.FileNotFound, - "File not found", - false + + async list(input: SandboxFileListInput): Promise { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ); + } + try { + const p = path.normalize(this.fullpath(input.path)); + const files = await this.client._sandbox.files.list(p); + return { + files: files.map((file) => ({ + name: file.name, + path: file.path.replace(this.folder, ''), + type: file.type === 'file' ? 'file' : 'directory', + })), + }; + } catch (e) { + if (e instanceof NotFoundError) { + throw new ClientError( + ClientErrorCode.FileNotFound, + 'Folder or file not found', + false, + ); + } + throw e; + } + } + + async read(input: SandboxFileReadInput): Promise { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ); + } + try { + const data = await this.client._sandbox.files.read(this.fullpath(input.path)); + if (!data) { + throw new ClientError(ClientErrorCode.FileNotFound, 'File not found', false); + } + + return { + data, + }; + } catch (e) { + if (e instanceof NotFoundError) { + throw new ClientError(ClientErrorCode.FileNotFound, 'File not found', false); + } + throw e; + } + } + + async rename(input: SandboxFileRenameInput): Promise { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ); + } + await this.client._sandbox.files.rename( + this.fullpath(input.oldPath), + this.fullpath(input.newPath), ); - } - throw e; + return {}; } - } - - async rename( - input: SandboxFileRenameInput - ): Promise { - if (!this.client._sandbox) { - throw new ClientError( - ClientErrorCode.ImplementationError, - "Sandbox is not instantiated. Call start() or resume() first.", - false - ); + + async stat(input: SandboxFileStatInput): Promise { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ); + } + const file = await this.client._sandbox.files.getInfo(this.fullpath(input.path)); + if (!file) { + throw new ClientError(ClientErrorCode.FileNotFound, 'File not found', false); + } + return { + type: file.type === 'file' ? 'file' : 'directory', + }; + } + + async write(input: SandboxFileWriteInput): Promise { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ); + } + const files: { path: string; data: string }[] = []; + for (const file of Array.isArray(input.files) ? input.files : [input.files]) { + if (!file.overwrite) { + const exists = await this.client._sandbox.files.exists(this.fullpath(file.path)); + if (exists) { + continue; + } + } + files.push({ + path: this.fullpath(file.path), + data: file.data, + }); + } + await this.client._sandbox.files.write(files); + return {}; } - await this.client._sandbox.files.rename( - this.fullpath(input.oldPath), - this.fullpath(input.newPath) - ); - return {}; - } - - async stat(input: SandboxFileStatInput): Promise { - if (!this.client._sandbox) { - throw new ClientError( - ClientErrorCode.ImplementationError, - "Sandbox is not instantiated. Call start() or resume() first.", - false - ); + + async watch(input: SandboxFileWatchInput): Promise { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ); + } + + const sandboxId = this.client._sandbox.sandboxId; + + if (!sandboxId) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not running. Call start() or resume() first.', + false, + ); + } + + if (!watchers.has(sandboxId)) { + watchers.set(sandboxId, new Watcher(this.client)); + } + + const watcher = watchers.get(sandboxId); + + // obviously this should never happen + if (!watcher) { + throw new ClientError(ClientErrorCode.ImplementationError, 'Watcher not found', false); + } + + if (watcher.off) { + await watcher.start({ + path: this.fullpath(input.path), + recursive: input.recursive, + excludePaths: input.excludePaths, + }); + } + + const events = await watcher.list(); + + return { + events: events.map((event) => ({ + path: event.path, + type: event.type, + })), + }; } - const file = await this.client._sandbox.files.getInfo( - this.fullpath(input.path) - ); - if (!file) { - throw new ClientError( - ClientErrorCode.FileNotFound, - "File not found", - false - ); + + protected fullpath(path: string): string { + return this.folder + (path.startsWith('/') ? '' : '/') + path; } - return { - type: file.type === "file" ? "file" : "directory", - }; - } - - async write(input: SandboxFileWriteInput): Promise { - if (!this.client._sandbox) { - throw new ClientError( - ClientErrorCode.ImplementationError, - "Sandbox is not instantiated. Call start() or resume() first.", - false - ); +} + +interface WatcherEvent { + path: string; + type: 'create' | 'update' | 'delete'; +} + +class Watcher { + protected readonly maxEvents = 500; + // longer timeout might lead to gateway timeouts + protected readonly promiseTimeout = 30000; // 30 seconds + protected readonly watchTimeout = 300000; // 5 minutes + + protected events: Array = []; + + protected _off: boolean = true; + protected _watchHandle: WatchHandle | null = null; + protected _timeout: NodeJS.Timeout | null = null; + protected _resolvers: Array<() => void> = []; + + constructor(protected readonly client: E2BClient) {} + + get off(): boolean { + return this._off; } - const files: { path: string; data: string }[] = []; - for (const file of Array.isArray(input.files) - ? input.files - : [input.files]) { - if (!file.overwrite) { - const exists = await this.client._sandbox.files.exists( - this.fullpath(file.path) + + async start(input: SandboxFileWatchInput): Promise { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ); + } + + if (!this._off) { + return; + } + + this._off = false; + let timeout: NodeJS.Timeout | null = null; + this._watchHandle = await this.client._sandbox.files.watchDir( + input.path, + (event) => { + if ( + input.excludePaths.some((exclude) => + event.name.startsWith(exclude.replace('**', '')), + ) + ) { + return; + } + + this.events.push({ + path: event.name, + type: + event.type === 'create' + ? 'create' + : event.type === 'remove' + ? 'delete' + : 'update', + }); + + // avoid memory leak + this.events = this.events.slice(0, this.maxEvents - 1); + + if (timeout) { + clearTimeout(timeout); + } + + // debounce the resolution of the promise + timeout = setTimeout(() => { + this._resolvers.forEach((resolve) => resolve()); + this._resolvers = []; + }, 300); + }, + { + // requestTimeoutMs: 0, + timeoutMs: 0, + recursive: input.recursive, + onExit: (err) => { + console.error('[coderouter] watcher exited – error: ', err || 'none'); + this.stop(); + }, + }, ); - if (exists) { - continue; - } - } - files.push({ - path: this.fullpath(file.path), - data: file.data, - }); } - await this.client._sandbox.files.write(files); - return {}; - } - protected fullpath(path: string): string { - return this.folder + (path.startsWith("/") ? "" : "/") + path; - } + async list(): Promise> { + if (this._timeout) { + clearTimeout(this._timeout); + this._timeout = null; + } + + // if there hasn't been any calls to `list` within the timeout, stop the watcher + this._timeout = setTimeout(() => { + this.stop(); + }, this.watchTimeout); + + // if there are no events, do not return yet + if (this.events.length === 0) { + let resolved = false; + await new Promise((resolve) => { + this._resolvers.push(() => { + if (!resolved) { + resolve(); + resolved = true; + } + }); + setTimeout(() => { + if (!resolved) { + resolve(); + resolved = true; + } + }, this.promiseTimeout); + }); + } + + const events = this.events; + this.events = []; + return events; + } + + async stop(): Promise { + if (this._watchHandle) { + await this._watchHandle.stop(); + this._watchHandle = null; + } + + this._off = true; + this.events = []; + } } diff --git a/apps/coderouter/src/server.ts b/apps/coderouter/src/server.ts index acdb46423c..9080268751 100644 --- a/apps/coderouter/src/server.ts +++ b/apps/coderouter/src/server.ts @@ -9,6 +9,7 @@ import { api_sandbox_file_list } from './api/sandbox/file/list'; import { api_sandbox_file_read } from './api/sandbox/file/read'; import { api_sandbox_file_rename } from './api/sandbox/file/rename'; import { api_sandbox_file_stat } from './api/sandbox/file/stat'; +import { api_sandbox_file_watch } from './api/sandbox/file/watch'; import { api_sandbox_file_write } from './api/sandbox/file/write'; import { api_sandbox_pause } from './api/sandbox/pause'; import { api_sandbox_resume } from './api/sandbox/resume'; @@ -81,6 +82,7 @@ api_sandbox_file_list(app); api_sandbox_file_read(app); api_sandbox_file_rename(app); api_sandbox_file_stat(app); +api_sandbox_file_watch(app); api_sandbox_file_write(app); app.doc('/openapi.json', { diff --git a/apps/web/client/src/components/store/editor/sandbox/index.ts b/apps/web/client/src/components/store/editor/sandbox/index.ts index fecfda418a..3d742380cc 100644 --- a/apps/web/client/src/components/store/editor/sandbox/index.ts +++ b/apps/web/client/src/components/store/editor/sandbox/index.ts @@ -118,7 +118,7 @@ export class SandboxManager { } } } - + console.log('----> watch files called?'); await this.watchFiles(); this._isIndexed = true; timer.log('Indexing completed successfully'); diff --git a/packages/code-provider/src/providers/coderouter/codegen/.openapi-generator/FILES b/packages/code-provider/src/providers/coderouter/codegen/.openapi-generator/FILES index 578934e713..05847e5220 100644 --- a/packages/code-provider/src/providers/coderouter/codegen/.openapi-generator/FILES +++ b/packages/code-provider/src/providers/coderouter/codegen/.openapi-generator/FILES @@ -1,4 +1,3 @@ -.openapi-generator-ignore apis/DefaultApi.ts apis/index.ts index.ts @@ -18,6 +17,9 @@ models/CoderouterApiSandboxFileReadPostRequest.ts models/CoderouterApiSandboxFileRenamePostRequest.ts models/CoderouterApiSandboxFileStatPost200Response.ts models/CoderouterApiSandboxFileStatPostRequest.ts +models/CoderouterApiSandboxFileWatchPost200Response.ts +models/CoderouterApiSandboxFileWatchPost200ResponseEventsInner.ts +models/CoderouterApiSandboxFileWatchPostRequest.ts models/CoderouterApiSandboxFileWritePostRequest.ts models/CoderouterApiSandboxFileWritePostRequestFilesInner.ts models/CoderouterApiSandboxUrlPost200Response.ts diff --git a/packages/code-provider/src/providers/coderouter/codegen/apis/DefaultApi.ts b/packages/code-provider/src/providers/coderouter/codegen/apis/DefaultApi.ts index 3db9023c3a..e3a84936a3 100644 --- a/packages/code-provider/src/providers/coderouter/codegen/apis/DefaultApi.ts +++ b/packages/code-provider/src/providers/coderouter/codegen/apis/DefaultApi.ts @@ -29,6 +29,8 @@ import type { CoderouterApiSandboxFileRenamePostRequest, CoderouterApiSandboxFileStatPost200Response, CoderouterApiSandboxFileStatPostRequest, + CoderouterApiSandboxFileWatchPost200Response, + CoderouterApiSandboxFileWatchPostRequest, CoderouterApiSandboxFileWritePostRequest, CoderouterApiSandboxUrlPost200Response, } from '../models/index'; @@ -63,6 +65,10 @@ import { CoderouterApiSandboxFileStatPost200ResponseToJSON, CoderouterApiSandboxFileStatPostRequestFromJSON, CoderouterApiSandboxFileStatPostRequestToJSON, + CoderouterApiSandboxFileWatchPost200ResponseFromJSON, + CoderouterApiSandboxFileWatchPost200ResponseToJSON, + CoderouterApiSandboxFileWatchPostRequestFromJSON, + CoderouterApiSandboxFileWatchPostRequestToJSON, CoderouterApiSandboxFileWritePostRequestFromJSON, CoderouterApiSandboxFileWritePostRequestToJSON, CoderouterApiSandboxUrlPost200ResponseFromJSON, @@ -105,6 +111,10 @@ export interface CoderouterApiSandboxFileStatPostOperationRequest { coderouterApiSandboxFileStatPostRequest?: CoderouterApiSandboxFileStatPostRequest; } +export interface CoderouterApiSandboxFileWatchPostOperationRequest { + coderouterApiSandboxFileWatchPostRequest?: CoderouterApiSandboxFileWatchPostRequest; +} + export interface CoderouterApiSandboxFileWritePostOperationRequest { coderouterApiSandboxFileWritePostRequest?: CoderouterApiSandboxFileWritePostRequest; } @@ -600,6 +610,60 @@ export class DefaultApi extends runtime.BaseAPI { return await response.value(); } + /** + */ + async coderouterApiSandboxFileWatchPostRaw( + requestParameters: CoderouterApiSandboxFileWatchPostOperationRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json'; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token('jwt', []); + + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } + } + + let urlPath = `/coderouter/api/sandbox/file/watch`; + + const response = await this.request( + { + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: CoderouterApiSandboxFileWatchPostRequestToJSON( + requestParameters['coderouterApiSandboxFileWatchPostRequest'], + ), + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response, (jsonValue) => + CoderouterApiSandboxFileWatchPost200ResponseFromJSON(jsonValue), + ); + } + + /** + */ + async coderouterApiSandboxFileWatchPost( + requestParameters: CoderouterApiSandboxFileWatchPostOperationRequest = {}, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.coderouterApiSandboxFileWatchPostRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + /** */ async coderouterApiSandboxFileWritePostRaw( diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileWatchPost200Response.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileWatchPost200Response.ts new file mode 100644 index 0000000000..e07f787383 --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileWatchPost200Response.ts @@ -0,0 +1,87 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +import type { CoderouterApiSandboxFileWatchPost200ResponseEventsInner } from './CoderouterApiSandboxFileWatchPost200ResponseEventsInner'; +import { + CoderouterApiSandboxFileWatchPost200ResponseEventsInnerFromJSON, + CoderouterApiSandboxFileWatchPost200ResponseEventsInnerFromJSONTyped, + CoderouterApiSandboxFileWatchPost200ResponseEventsInnerToJSON, + CoderouterApiSandboxFileWatchPost200ResponseEventsInnerToJSONTyped, +} from './CoderouterApiSandboxFileWatchPost200ResponseEventsInner'; + +/** + * + * @export + * @interface CoderouterApiSandboxFileWatchPost200Response + */ +export interface CoderouterApiSandboxFileWatchPost200Response { + /** + * + * @type {Array} + * @memberof CoderouterApiSandboxFileWatchPost200Response + */ + events: Array; +} + +/** + * Check if a given object implements the CoderouterApiSandboxFileWatchPost200Response interface. + */ +export function instanceOfCoderouterApiSandboxFileWatchPost200Response( + value: object, +): value is CoderouterApiSandboxFileWatchPost200Response { + if (!('events' in value) || value['events'] === undefined) return false; + return true; +} + +export function CoderouterApiSandboxFileWatchPost200ResponseFromJSON( + json: any, +): CoderouterApiSandboxFileWatchPost200Response { + return CoderouterApiSandboxFileWatchPost200ResponseFromJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileWatchPost200ResponseFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiSandboxFileWatchPost200Response { + if (json == null) { + return json; + } + return { + events: (json['events'] as Array).map( + CoderouterApiSandboxFileWatchPost200ResponseEventsInnerFromJSON, + ), + }; +} + +export function CoderouterApiSandboxFileWatchPost200ResponseToJSON( + json: any, +): CoderouterApiSandboxFileWatchPost200Response { + return CoderouterApiSandboxFileWatchPost200ResponseToJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileWatchPost200ResponseToJSONTyped( + value?: CoderouterApiSandboxFileWatchPost200Response | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + events: (value['events'] as Array).map( + CoderouterApiSandboxFileWatchPost200ResponseEventsInnerToJSON, + ), + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileWatchPost200ResponseEventsInner.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileWatchPost200ResponseEventsInner.ts new file mode 100644 index 0000000000..8f54a275dd --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileWatchPost200ResponseEventsInner.ts @@ -0,0 +1,95 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface CoderouterApiSandboxFileWatchPost200ResponseEventsInner + */ +export interface CoderouterApiSandboxFileWatchPost200ResponseEventsInner { + /** + * + * @type {string} + * @memberof CoderouterApiSandboxFileWatchPost200ResponseEventsInner + */ + path: string; + /** + * + * @type {string} + * @memberof CoderouterApiSandboxFileWatchPost200ResponseEventsInner + */ + type: CoderouterApiSandboxFileWatchPost200ResponseEventsInnerTypeEnum; +} + +/** + * @export + */ +export const CoderouterApiSandboxFileWatchPost200ResponseEventsInnerTypeEnum = { + Create: 'create', + Update: 'update', + Delete: 'delete', +} as const; +export type CoderouterApiSandboxFileWatchPost200ResponseEventsInnerTypeEnum = + (typeof CoderouterApiSandboxFileWatchPost200ResponseEventsInnerTypeEnum)[keyof typeof CoderouterApiSandboxFileWatchPost200ResponseEventsInnerTypeEnum]; + +/** + * Check if a given object implements the CoderouterApiSandboxFileWatchPost200ResponseEventsInner interface. + */ +export function instanceOfCoderouterApiSandboxFileWatchPost200ResponseEventsInner( + value: object, +): value is CoderouterApiSandboxFileWatchPost200ResponseEventsInner { + if (!('path' in value) || value['path'] === undefined) return false; + if (!('type' in value) || value['type'] === undefined) return false; + return true; +} + +export function CoderouterApiSandboxFileWatchPost200ResponseEventsInnerFromJSON( + json: any, +): CoderouterApiSandboxFileWatchPost200ResponseEventsInner { + return CoderouterApiSandboxFileWatchPost200ResponseEventsInnerFromJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileWatchPost200ResponseEventsInnerFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiSandboxFileWatchPost200ResponseEventsInner { + if (json == null) { + return json; + } + return { + path: json['path'], + type: json['type'], + }; +} + +export function CoderouterApiSandboxFileWatchPost200ResponseEventsInnerToJSON( + json: any, +): CoderouterApiSandboxFileWatchPost200ResponseEventsInner { + return CoderouterApiSandboxFileWatchPost200ResponseEventsInnerToJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileWatchPost200ResponseEventsInnerToJSONTyped( + value?: CoderouterApiSandboxFileWatchPost200ResponseEventsInner | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + path: value['path'], + type: value['type'], + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileWatchPostRequest.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileWatchPostRequest.ts new file mode 100644 index 0000000000..a007eae373 --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxFileWatchPostRequest.ts @@ -0,0 +1,93 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface CoderouterApiSandboxFileWatchPostRequest + */ +export interface CoderouterApiSandboxFileWatchPostRequest { + /** + * The path of the file to write. + * @type {string} + * @memberof CoderouterApiSandboxFileWatchPostRequest + */ + path: string; + /** + * Whether to watch the file recursively. + * @type {boolean} + * @memberof CoderouterApiSandboxFileWatchPostRequest + */ + recursive: boolean; + /** + * The paths to exclude from the watch. + * @type {Array} + * @memberof CoderouterApiSandboxFileWatchPostRequest + */ + excludePaths: Array; +} + +/** + * Check if a given object implements the CoderouterApiSandboxFileWatchPostRequest interface. + */ +export function instanceOfCoderouterApiSandboxFileWatchPostRequest( + value: object, +): value is CoderouterApiSandboxFileWatchPostRequest { + if (!('path' in value) || value['path'] === undefined) return false; + if (!('recursive' in value) || value['recursive'] === undefined) return false; + if (!('excludePaths' in value) || value['excludePaths'] === undefined) return false; + return true; +} + +export function CoderouterApiSandboxFileWatchPostRequestFromJSON( + json: any, +): CoderouterApiSandboxFileWatchPostRequest { + return CoderouterApiSandboxFileWatchPostRequestFromJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileWatchPostRequestFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiSandboxFileWatchPostRequest { + if (json == null) { + return json; + } + return { + path: json['path'], + recursive: json['recursive'], + excludePaths: json['excludePaths'], + }; +} + +export function CoderouterApiSandboxFileWatchPostRequestToJSON( + json: any, +): CoderouterApiSandboxFileWatchPostRequest { + return CoderouterApiSandboxFileWatchPostRequestToJSONTyped(json, false); +} + +export function CoderouterApiSandboxFileWatchPostRequestToJSONTyped( + value?: CoderouterApiSandboxFileWatchPostRequest | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + path: value['path'], + recursive: value['recursive'], + excludePaths: value['excludePaths'], + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/index.ts b/packages/code-provider/src/providers/coderouter/codegen/models/index.ts index 00b5eec595..6cbf9d1106 100644 --- a/packages/code-provider/src/providers/coderouter/codegen/models/index.ts +++ b/packages/code-provider/src/providers/coderouter/codegen/models/index.ts @@ -16,6 +16,9 @@ export * from './CoderouterApiSandboxFileReadPostRequest'; export * from './CoderouterApiSandboxFileRenamePostRequest'; export * from './CoderouterApiSandboxFileStatPost200Response'; export * from './CoderouterApiSandboxFileStatPostRequest'; +export * from './CoderouterApiSandboxFileWatchPost200Response'; +export * from './CoderouterApiSandboxFileWatchPost200ResponseEventsInner'; +export * from './CoderouterApiSandboxFileWatchPostRequest'; export * from './CoderouterApiSandboxFileWritePostRequest'; export * from './CoderouterApiSandboxFileWritePostRequestFilesInner'; export * from './CoderouterApiSandboxUrlPost200Response'; diff --git a/packages/code-provider/src/providers/coderouter/index.ts b/packages/code-provider/src/providers/coderouter/index.ts index 7d0a9dde28..3f1138e237 100644 --- a/packages/code-provider/src/providers/coderouter/index.ts +++ b/packages/code-provider/src/providers/coderouter/index.ts @@ -97,11 +97,6 @@ export class CoderouterProvider extends Provider { this.options.userId, ); this.jwt = res.jwt ?? null; - - if (typeof window !== 'undefined') { - const url = await this.getProjectUrl({ args: {} }); - console.log('----> url', url); - } } return {}; } @@ -256,8 +251,23 @@ export class CoderouterProvider extends Provider { } async watchFiles(input: WatchFilesInput): Promise { + const watcher = new CoderouterFileWatcher(this.api, () => this.requestInitOverrides()); + + await watcher.start(input); + + if (input.onFileChange) { + watcher.registerEventCallback(async (event) => { + if (input.onFileChange) { + await input.onFileChange({ + type: event.type, + paths: event.paths, + }); + } + }); + } + return { - watcher: new CoderouterFileWatcher(), + watcher, }; } @@ -381,16 +391,85 @@ export class CoderouterProvider extends Provider { } export class CoderouterFileWatcher extends ProviderFileWatcher { - start(input: WatchFilesInput): Promise { - return Promise.resolve(); + protected callbacks: Array<(event: WatchEvent) => Promise> = []; + protected abortController: AbortController | null = null; + + constructor( + private readonly api: OpenAPI.DefaultApi, + private readonly requestInitOverrides: () => RequestInit, + ) { + super(); } - stop(): Promise { - return Promise.resolve(); + protected off = true; + async start(input: WatchFilesInput): Promise { + if (!this.off) { + return; + } + + this.off = false; + + const tick = async () => { + if (this.off) { + return; + } + + if (this.abortController) { + this.abortController.abort(); + } + + this.abortController = new AbortController(); + + try { + const body = await this.api.coderouterApiSandboxFileWatchPost( + { + coderouterApiSandboxFileWatchPostRequest: { + path: input.args.path, + recursive: input.args.recursive ?? true, + excludePaths: input.args.excludes ?? [], + }, + }, + { + ...this.requestInitOverrides(), + signal: this.abortController?.signal, + }, + ); + + // convert the events to a map of type to paths as the defined by the product + const events = body.events.reduce( + (acc, event) => { + acc[event.type] = [...(acc[event.type] || []), event.path]; + return acc; + }, + {} as Record, + ); + + for (const [type, paths] of Object.entries(events)) { + this.callbacks.forEach(async (callback) => { + await callback({ + type: + type === 'create' ? 'add' : type === 'delete' ? 'remove' : 'change', + paths, + }); + }); + } + } catch (err) { + console.error('[coderouter] poll error', err); + } finally { + // schedule next poll immediately + setTimeout(tick, 200); + } + }; + + tick(); + } + + async stop(): Promise { + this.off = true; } registerEventCallback(callback: (event: WatchEvent) => Promise): void { - // TODO: Implement + this.callbacks.push(callback); } } diff --git a/packages/code-provider/src/providers/codesandbox/utils/utils.ts b/packages/code-provider/src/providers/codesandbox/utils/utils.ts index 2e94b74a0b..938eeec063 100644 --- a/packages/code-provider/src/providers/codesandbox/utils/utils.ts +++ b/packages/code-provider/src/providers/codesandbox/utils/utils.ts @@ -25,8 +25,6 @@ export async function readRemoteFile( ): Promise { try { if (isImageFile(filePath)) { - console.log('reading image file', filePath); - const content = await client.fs.readFile(filePath); return getFileFromContent(filePath, content); } else { From 6139f121a3a8f5caefe278952d39515f3be2008f Mon Sep 17 00:00:00 2001 From: Thomas Potaire Date: Sun, 7 Sep 2025 13:28:30 -0700 Subject: [PATCH 3/5] implementing terminal --- apps/coderouter/package.json | 1 + .../sandbox/terminal/command/index.test.ts | 0 .../src/api/sandbox/terminal/command/index.ts | 72 ++++ .../api/sandbox/terminal/create/index.test.ts | 0 .../src/api/sandbox/terminal/create/index.ts | 64 +++ .../src/api/sandbox/terminal/kill/index.ts | 56 +++ .../src/api/sandbox/terminal/open/index.ts | 81 ++++ .../src/api/sandbox/terminal/run/index.ts | 61 +++ .../src/api/sandbox/terminal/write/index.ts | 59 +++ .../src/provider/definition/sandbox/index.ts | 68 ++-- .../definition/sandbox/terminal/index.ts | 68 ++++ apps/coderouter/src/provider/e2b/index.ts | 25 +- .../src/provider/e2b/sandbox/index.ts | 7 +- .../provider/e2b/sandbox/terminal/index.ts | 303 ++++++++++++++ apps/coderouter/src/server.ts | 17 +- apps/nginx/conf.d/server.conf | 11 + .../components/store/editor/sandbox/index.ts | 34 +- bun.lock | 6 + packages/code-provider/package.json | 1 + .../codegen/.openapi-generator/FILES | 9 + .../coderouter/codegen/apis/DefaultApi.ts | 383 ++++++++++++++++++ ...piSandboxTerminalCommandPost200Response.ts | 111 +++++ ...terApiSandboxTerminalCommandPostRequest.ts | 75 ++++ ...uterApiSandboxTerminalCreatePostRequest.ts | 84 ++++ ...erApiSandboxTerminalKillPost200Response.ts | 75 ++++ ...routerApiSandboxTerminalKillPostRequest.ts | 75 ++++ ...erApiSandboxTerminalOpenPost200Response.ts | 75 ++++ ...routerApiSandboxTerminalOpenPostRequest.ts | 75 ++++ ...terApiSandboxTerminalRunPost200Response.ts | 75 ++++ ...erouterApiSandboxTerminalRunPostRequest.ts | 84 ++++ ...rApiSandboxTerminalWritePost200Response.ts | 75 ++++ ...outerApiSandboxTerminalWritePostRequest.ts | 84 ++++ .../coderouter/codegen/models/index.ts | 9 + .../src/providers/coderouter/index.ts | 145 ++++++- 34 files changed, 2293 insertions(+), 75 deletions(-) create mode 100644 apps/coderouter/src/api/sandbox/terminal/command/index.test.ts create mode 100644 apps/coderouter/src/api/sandbox/terminal/command/index.ts create mode 100644 apps/coderouter/src/api/sandbox/terminal/create/index.test.ts create mode 100644 apps/coderouter/src/api/sandbox/terminal/create/index.ts create mode 100644 apps/coderouter/src/api/sandbox/terminal/kill/index.ts create mode 100644 apps/coderouter/src/api/sandbox/terminal/open/index.ts create mode 100644 apps/coderouter/src/api/sandbox/terminal/run/index.ts create mode 100644 apps/coderouter/src/api/sandbox/terminal/write/index.ts create mode 100644 apps/coderouter/src/provider/definition/sandbox/terminal/index.ts create mode 100644 apps/coderouter/src/provider/e2b/sandbox/terminal/index.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalCommandPost200Response.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalCommandPostRequest.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalCreatePostRequest.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalKillPost200Response.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalKillPostRequest.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalOpenPost200Response.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalOpenPostRequest.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalRunPost200Response.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalRunPostRequest.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalWritePost200Response.ts create mode 100644 packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalWritePostRequest.ts diff --git a/apps/coderouter/package.json b/apps/coderouter/package.json index c6e37bff3c..7762e2655d 100644 --- a/apps/coderouter/package.json +++ b/apps/coderouter/package.json @@ -25,6 +25,7 @@ "jsonwebtoken": "^9.0.2", "mysql2": "^3.9.7", "pg": "^8.11.5", + "uuid": "^12.0.0", "zod": "^4.0.17" }, "devDependencies": { diff --git a/apps/coderouter/src/api/sandbox/terminal/command/index.test.ts b/apps/coderouter/src/api/sandbox/terminal/command/index.test.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/coderouter/src/api/sandbox/terminal/command/index.ts b/apps/coderouter/src/api/sandbox/terminal/command/index.ts new file mode 100644 index 0000000000..afe2cab545 --- /dev/null +++ b/apps/coderouter/src/api/sandbox/terminal/command/index.ts @@ -0,0 +1,72 @@ +import { + SandboxTerminalCommandInput, + SandboxTerminalCommandOutput, +} from '@/provider/definition/sandbox/terminal'; +import { LocalHono } from '@/server'; +import { JwtAuthResponses } from '@/util/auth'; +import { createRoute, z } from '@hono/zod-openapi'; +import { env } from 'bun'; + +const BodySchema: z.ZodType = z.object({ + command: z.string().openapi({ + description: 'The command to run.', + example: 'ls -la', + }), +}); + +const ResponseSchema: z.ZodType = z.object({ + is_error: z.boolean().openapi({ + description: 'Whether the command was successful.', + example: true, + }), + output: z.string().openapi({ + description: 'The output of the command.', + example: 'ls -la', + }), + stdout: z.string().openapi({ + description: 'The stdout of the command.', + example: 'ls -la', + }), + stderr: z.string().openapi({ + description: 'The stderr of the command.', + example: 'ls -la', + }), + exit_code: z.number().openapi({ + description: 'The exit code of the command.', + example: 0, + }), +}); + +const route = createRoute({ + method: 'post', + path: env.URL_PATH_PREFIX + '/api/sandbox/terminal/command', + security: [{ jwt: [] }], + request: { + body: { + content: { + 'application/json': { + schema: BodySchema, + }, + }, + }, + }, + responses: { + 200: { + content: { + 'application/json': { + schema: ResponseSchema, + }, + }, + description: 'Run a command in the sandbox.', + }, + ...JwtAuthResponses, + }, +}); + +export function api_sandbox_terminal_command(app: LocalHono) { + app.openapi(route, async (c) => { + const body = await c.req.valid('json'); + const result = await c.get('client').sandbox.terminal.command(body); + return c.json(result, 200); + }); +} diff --git a/apps/coderouter/src/api/sandbox/terminal/create/index.test.ts b/apps/coderouter/src/api/sandbox/terminal/create/index.test.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/coderouter/src/api/sandbox/terminal/create/index.ts b/apps/coderouter/src/api/sandbox/terminal/create/index.ts new file mode 100644 index 0000000000..ebaabccc20 --- /dev/null +++ b/apps/coderouter/src/api/sandbox/terminal/create/index.ts @@ -0,0 +1,64 @@ +import { + SandboxTerminalCreateInput, + SandboxTerminalCreateOutput, +} from '@/provider/definition/sandbox/terminal'; +import { LocalHono } from '@/server'; +import { JwtAuthResponses } from '@/util/auth'; +import { createRoute, z } from '@hono/zod-openapi'; +import { env } from 'bun'; + +const BodySchema: z.ZodType = z.object({ + terminalId: z.string().openapi({ + description: 'The ID of the terminal.', + example: '00000000-0000-0000-0000-000000000000 or any unique string', + }), + name: z.string().openapi({ + description: 'The name of the terminal.', + example: 'My Terminal', + }), +}); + +const ResponseSchema: z.ZodType = z.object({ + terminalId: z.string().openapi({ + description: 'The ID of the terminal.', + example: '00000000-0000-0000-0000-000000000000 or any unique string', + }), + name: z.string().openapi({ + description: 'The name of the terminal.', + example: 'My Terminal', + }), +}); + +const route = createRoute({ + method: 'post', + path: env.URL_PATH_PREFIX + '/api/sandbox/terminal/create', + security: [{ jwt: [] }], + request: { + body: { + content: { + 'application/json': { + schema: BodySchema, + }, + }, + }, + }, + responses: { + 200: { + content: { + 'application/json': { + schema: ResponseSchema, + }, + }, + description: 'Create a terminal in the sandbox.', + }, + ...JwtAuthResponses, + }, +}); + +export function api_sandbox_terminal_create(app: LocalHono) { + app.openapi(route, async (c) => { + const body = await c.req.valid('json'); + const result = await c.get('client').sandbox.terminal.create(body); + return c.json(result, 200); + }); +} diff --git a/apps/coderouter/src/api/sandbox/terminal/kill/index.ts b/apps/coderouter/src/api/sandbox/terminal/kill/index.ts new file mode 100644 index 0000000000..b978d87525 --- /dev/null +++ b/apps/coderouter/src/api/sandbox/terminal/kill/index.ts @@ -0,0 +1,56 @@ +import { + SandboxTerminalKillInput, + SandboxTerminalKillOutput, +} from '@/provider/definition/sandbox/terminal'; +import { LocalHono } from '@/server'; +import { JwtAuthResponses } from '@/util/auth'; +import { createRoute, z } from '@hono/zod-openapi'; +import { env } from 'bun'; + +const BodySchema: z.ZodType = z.object({ + terminalId: z.string().openapi({ + description: 'The ID of the terminal.', + example: '00000000-0000-0000-0000-000000000000 or any unique string', + }), +}); + +const ResponseSchema: z.ZodType = z.object({ + output: z.string().openapi({ + description: 'The output of the kill command.', + example: 'Terminal killed', + }), +}); + +const route = createRoute({ + method: 'post', + path: env.URL_PATH_PREFIX + '/api/sandbox/terminal/kill', + security: [{ jwt: [] }], + request: { + body: { + content: { + 'application/json': { + schema: BodySchema, + }, + }, + }, + }, + responses: { + 200: { + content: { + 'application/json': { + schema: ResponseSchema, + }, + }, + description: 'Kill a terminal in the sandbox.', + }, + ...JwtAuthResponses, + }, +}); + +export function api_sandbox_terminal_kill(app: LocalHono) { + app.openapi(route, async (c) => { + const body = await c.req.valid('json'); + const result = await c.get('client').sandbox.terminal.kill(body); + return c.json(result, 200); + }); +} diff --git a/apps/coderouter/src/api/sandbox/terminal/open/index.ts b/apps/coderouter/src/api/sandbox/terminal/open/index.ts new file mode 100644 index 0000000000..8d6ba21cc3 --- /dev/null +++ b/apps/coderouter/src/api/sandbox/terminal/open/index.ts @@ -0,0 +1,81 @@ +// import { +// SandboxTerminalOpenInput, +// SandboxTerminalOpenOutput, +// } from '@/provider/definition/sandbox/terminal'; +import { SandboxTerminalOpenOutput } from '@/provider/definition/sandbox/terminal'; +import { LocalHono } from '@/server'; +import { JwtAuthResponses } from '@/util/auth'; +import { createRoute, z } from '@hono/zod-openapi'; +import { env, sleep } from 'bun'; +import { streamSSE } from 'hono/streaming'; +import { v4 as uuid } from 'uuid'; + +const ParamsSchema = z.object({ + terminalId: z.string().openapi({ + description: 'The ID of the terminal.', + example: '00000000-0000-0000-0000-000000000000 or any unique string', + }), +}); + +const route = createRoute({ + method: 'get', + path: env.URL_PATH_PREFIX + '/api/sandbox/terminal/open', + security: [{ jwt: [] }], + request: { + query: ParamsSchema, + }, + responses: { + 200: { + content: { + 'text/event-stream': { + schema: z.string().openapi({ + description: + 'A stream of server-sent events (not JSON array, continuous text).', + }), + }, + }, + description: 'Return the output of a terminal in the sandbox. Design for long-polling', + }, + ...JwtAuthResponses, + }, +}); + +export function api_sandbox_terminal_open(app: LocalHono) { + app.openapi(route, async (c) => { + const query = await c.req.valid('query'); + return streamSSE(c, async (stream) => { + // Send a "connected" message immediately + await stream.writeSSE({ + id: uuid(), + event: 'status', + data: 'Connected to endpoint.', + }); + + const onOutput = (res: SandboxTerminalOpenOutput) => { + console.log(res.output); + stream.writeSSE({ + id: res.id, + event: 'message', + data: JSON.stringify({ output: res.output }), + }); + }; + + const { close } = await c.get('client').sandbox.terminal.open(query, onOutput); + + let open = true; + stream.onAbort(() => { + close(); + open = false; + }); + + while (open) { + await stream.writeSSE({ + id: uuid(), + event: 'status', + data: 'Endpoint is still open.', + }); + await sleep(5000); + } + }); + }); +} diff --git a/apps/coderouter/src/api/sandbox/terminal/run/index.ts b/apps/coderouter/src/api/sandbox/terminal/run/index.ts new file mode 100644 index 0000000000..390c12df60 --- /dev/null +++ b/apps/coderouter/src/api/sandbox/terminal/run/index.ts @@ -0,0 +1,61 @@ +import { + SandboxTerminalRunInput, + SandboxTerminalRunOutput, +} from '@/provider/definition/sandbox/terminal'; +import { LocalHono } from '@/server'; +import { JwtAuthResponses } from '@/util/auth'; +import { createRoute, z } from '@hono/zod-openapi'; +import { env } from 'bun'; + +const BodySchema: z.ZodType = z.object({ + terminalId: z.string().openapi({ + description: 'The ID of the terminal.', + example: '00000000-0000-0000-0000-000000000000 or any unique string', + }), + input: z.string().openapi({ + description: 'The name of the terminal.', + example: 'My Terminal', + }), +}); + +const ResponseSchema: z.ZodType = z.object({ + output: z.string().openapi({ + description: + 'The output of the run command. The output includes lines before and after the command execution.', + example: 'My Terminal', + }), +}); + +const route = createRoute({ + method: 'post', + path: env.URL_PATH_PREFIX + '/api/sandbox/terminal/run', + security: [{ jwt: [] }], + request: { + body: { + content: { + 'application/json': { + schema: BodySchema, + }, + }, + }, + }, + responses: { + 200: { + content: { + 'application/json': { + schema: ResponseSchema, + }, + }, + description: 'Run a terminal in the sandbox.', + }, + ...JwtAuthResponses, + }, +}); + +export function api_sandbox_terminal_run(app: LocalHono) { + app.openapi(route, async (c) => { + const body = await c.req.valid('json'); + const result = await c.get('client').sandbox.terminal.run(body); + return c.json(result, 200); + }); +} diff --git a/apps/coderouter/src/api/sandbox/terminal/write/index.ts b/apps/coderouter/src/api/sandbox/terminal/write/index.ts new file mode 100644 index 0000000000..6258df7e3f --- /dev/null +++ b/apps/coderouter/src/api/sandbox/terminal/write/index.ts @@ -0,0 +1,59 @@ +import { + SandboxTerminalWriteInput, + SandboxTerminalWriteOutput, +} from '@/provider/definition/sandbox/terminal'; +import { LocalHono } from '@/server'; +import { JwtAuthResponses } from '@/util/auth'; +import { createRoute, z } from '@hono/zod-openapi'; +import { env } from 'bun'; + +const BodySchema: z.ZodType = z.object({ + terminalId: z.string().openapi({ + description: 'The ID of the terminal.', + example: '00000000-0000-0000-0000-000000000000 or any unique string', + }), + input: z.string().openapi({ + description: 'The input to the write command.', + example: 'ls -la', + }), +}); + +const ResponseSchema: z.ZodType = z.object({ + output: z.string().openapi({ + description: 'The output of the write command.', + }), +}); + +const route = createRoute({ + method: 'post', + path: env.URL_PATH_PREFIX + '/api/sandbox/terminal/write', + security: [{ jwt: [] }], + request: { + body: { + content: { + 'application/json': { + schema: BodySchema, + }, + }, + }, + }, + responses: { + 200: { + content: { + 'application/json': { + schema: ResponseSchema, + }, + }, + description: 'Write a terminal in the sandbox.', + }, + ...JwtAuthResponses, + }, +}); + +export function api_sandbox_terminal_write(app: LocalHono) { + app.openapi(route, async (c) => { + const body = await c.req.valid('json'); + const result = await c.get('client').sandbox.terminal.write(body); + return c.json(result, 200); + }); +} diff --git a/apps/coderouter/src/provider/definition/sandbox/index.ts b/apps/coderouter/src/provider/definition/sandbox/index.ts index 157fc2258d..d572db7c57 100644 --- a/apps/coderouter/src/provider/definition/sandbox/index.ts +++ b/apps/coderouter/src/provider/definition/sandbox/index.ts @@ -1,66 +1,68 @@ -import { Client } from "../index"; -import { SandboxFile } from "./file"; +import { Client } from '../index'; +import { SandboxFile } from './file'; +import { SandboxTerminal } from './terminal'; export interface SandboxCreateInput { - // the sandbox ID is fed from the JWT token - // id: string; - // specify your own template ID to start up your sandbox faster - templateId: string; - // customize your sandbox metadata - metadata: Record; + // the sandbox ID is fed from the JWT token + // id: string; + // specify your own template ID to start up your sandbox faster + templateId: string; + // customize your sandbox metadata + metadata: Record; } export interface SandboxCreateOutput { - externalId: string; + externalId: string; } export interface SandboxGetInput { - // the sandbox ID is fed from the JWT token - // id: string; + // the sandbox ID is fed from the JWT token + // id: string; } export interface SandboxGetOutput { - id: string; - externalId: string; + id: string; + externalId: string; } export interface SandboxPauseInput { - // the sandbox ID is fed from the JWT token - // id: string; + // the sandbox ID is fed from the JWT token + // id: string; } export interface SandboxPauseOutput {} export interface SandboxResumeInput { - // the sandbox ID is fed from the JWT token - // id: string; + // the sandbox ID is fed from the JWT token + // id: string; } export interface SandboxResumeOutput {} export interface SandboxStopInput { - // the sandbox ID is fed from the JWT token - // id: string; + // the sandbox ID is fed from the JWT token + // id: string; } export interface SandboxStopOutput {} export interface SandboxUrlInput { - // the sandbox ID is fed from the JWT token - // id: string; + // the sandbox ID is fed from the JWT token + // id: string; } export interface SandboxUrlOutput { - url: string; + url: string; } export abstract class Sandbox { - public abstract readonly file: SandboxFile; + public abstract readonly file: SandboxFile; + public abstract readonly terminal: SandboxTerminal; - constructor(protected readonly client: T) {} + constructor(protected readonly client: T) {} - // called when the class is instantiated - // use this to resume the sandbox, bump the timeout and/or connect to the sandbox - abstract beforeSandboxCall(): Promise; + // called when the class is instantiated + // use this to resume the sandbox, bump the timeout and/or connect to the sandbox + abstract beforeSandboxCall(): Promise; - abstract create(input: SandboxCreateInput): Promise; - abstract get(input: SandboxGetInput): Promise; - abstract pause(input: SandboxPauseInput): Promise; - abstract resume(input: SandboxResumeInput): Promise; - abstract stop(input: SandboxStopInput): Promise; - abstract url(input: SandboxUrlInput): Promise; + abstract create(input: SandboxCreateInput): Promise; + abstract get(input: SandboxGetInput): Promise; + abstract pause(input: SandboxPauseInput): Promise; + abstract resume(input: SandboxResumeInput): Promise; + abstract stop(input: SandboxStopInput): Promise; + abstract url(input: SandboxUrlInput): Promise; } diff --git a/apps/coderouter/src/provider/definition/sandbox/terminal/index.ts b/apps/coderouter/src/provider/definition/sandbox/terminal/index.ts new file mode 100644 index 0000000000..73843a50d2 --- /dev/null +++ b/apps/coderouter/src/provider/definition/sandbox/terminal/index.ts @@ -0,0 +1,68 @@ +import { Client } from '../../index'; + +export interface SandboxTerminalCommandInput { + command: string; +} +export interface SandboxTerminalCommandOutput { + is_error: boolean; + output: string; + stdout?: string; + stderr?: string; + exit_code?: number; +} + +export interface SandboxTerminalCreateInput { + terminalId: string; + name?: string; +} +export interface SandboxTerminalCreateOutput { + terminalId: string; + name?: string; +} + +export interface SandboxTerminalOpenInput { + terminalId: string; +} +export interface SandboxTerminalOpenOutput { + id: string; + output: string; +} + +export interface SandboxTerminalWriteInput { + terminalId: string; + input: string; +} +export interface SandboxTerminalWriteOutput { + output: string; +} + +export interface SandboxTerminalKillInput { + terminalId: string; +} +export interface SandboxTerminalKillOutput { + output: string; +} + +export interface SandboxTerminalRunInput { + terminalId: string; + input: string; +} +export interface SandboxTerminalRunOutput { + output: string; +} + +export abstract class SandboxTerminal { + constructor(protected readonly client: T) {} + + // act like a static function, does not open a new terminal + abstract command(input: SandboxTerminalCommandInput): Promise; + + abstract create(input: SandboxTerminalCreateInput): Promise; + abstract open( + input: SandboxTerminalOpenInput, + onOutput: (output: SandboxTerminalOpenOutput) => void, + ): Promise<{ close: () => void }>; + abstract write(input: SandboxTerminalWriteInput): Promise; + abstract run(input: SandboxTerminalRunInput): Promise; + abstract kill(input: SandboxTerminalKillInput): Promise; +} diff --git a/apps/coderouter/src/provider/e2b/index.ts b/apps/coderouter/src/provider/e2b/index.ts index acb9fa9ab9..94c14ef7ed 100644 --- a/apps/coderouter/src/provider/e2b/index.ts +++ b/apps/coderouter/src/provider/e2b/index.ts @@ -1,16 +1,19 @@ -import { Sandbox as _Sandbox } from "@e2b/code-interpreter"; -import { Client, ClientOptions } from "../definition"; -import { E2BSandbox } from "./sandbox"; +import { Sandbox as _Sandbox } from '@e2b/code-interpreter'; +import { Client, ClientOptions } from '../definition'; +import { E2BSandbox } from './sandbox'; export class E2BClient extends Client { - public readonly sandbox: E2BSandbox; + public readonly sandbox: E2BSandbox; - // property is initialized by the sandbox class - // this is the E2B sandbox instance - _sandbox?: _Sandbox; + // property is initialized by the sandbox class + // this is the E2B sandbox instance + _sandbox?: _Sandbox; - constructor(options: ClientOptions, public readonly apiKey: string) { - super(options); - this.sandbox = new E2BSandbox(this); - } + constructor( + options: ClientOptions, + public readonly apiKey: string, + ) { + super(options); + this.sandbox = new E2BSandbox(this); + } } diff --git a/apps/coderouter/src/provider/e2b/sandbox/index.ts b/apps/coderouter/src/provider/e2b/sandbox/index.ts index 5931e71652..37ffc590c9 100644 --- a/apps/coderouter/src/provider/e2b/sandbox/index.ts +++ b/apps/coderouter/src/provider/e2b/sandbox/index.ts @@ -17,20 +17,24 @@ import { import { Sandbox as _E2BSandbox } from '@e2b/code-interpreter'; import { ClientError, ClientErrorCode } from '@/provider/definition'; import { E2BSandboxFile } from './file'; +import { E2BSandboxTerminal } from './terminal'; export class E2BSandbox extends Sandbox { public readonly file: E2BSandboxFile; + public readonly terminal: E2BSandboxTerminal; constructor(protected readonly client: E2BClient) { super(client); this.file = new E2BSandboxFile(this.client); + this.terminal = new E2BSandboxTerminal(this.client); } async beforeSandboxCall(): Promise { const e2bSandboxId = (await this.get({})).externalId; this.client._sandbox = await _E2BSandbox.connect(e2bSandboxId); // bump the timeout to 5 minutes - this.client._sandbox.setTimeout(1000 * 60 * 5); + // this.client._sandbox.setTimeout(1000 * 60 * 5); + this.client._sandbox.setTimeout(1000 * 60 * 30); } async create(input: SandboxCreateInput): Promise { @@ -57,6 +61,7 @@ export class E2BSandbox extends Sandbox { } this.client._sandbox = await _E2BSandbox.create(input.templateId, { apiKey: this.client.apiKey, + timeoutMs: 1000 * 60 * 30, metadata, }); return { diff --git a/apps/coderouter/src/provider/e2b/sandbox/terminal/index.ts b/apps/coderouter/src/provider/e2b/sandbox/terminal/index.ts new file mode 100644 index 0000000000..5efc0e1b07 --- /dev/null +++ b/apps/coderouter/src/provider/e2b/sandbox/terminal/index.ts @@ -0,0 +1,303 @@ +import { ClientError, ClientErrorCode } from '@/provider/definition'; +import { E2BClient } from '../..'; +import { + SandboxTerminal, + SandboxTerminalCommandInput, + SandboxTerminalCommandOutput, + SandboxTerminalCreateInput, + SandboxTerminalCreateOutput, + SandboxTerminalOpenInput, + SandboxTerminalOpenOutput, + SandboxTerminalWriteInput, + SandboxTerminalWriteOutput, + SandboxTerminalRunInput, + SandboxTerminalRunOutput, + SandboxTerminalKillInput, + SandboxTerminalKillOutput, +} from '../../../definition/sandbox/terminal'; +import { CommandExitError, CommandHandle } from '@e2b/code-interpreter'; +import { v4 as uuid } from 'uuid'; + +// sandboxId -> terminalId -> TerminalWatcher +const terminalWatchers: Map> = new Map(); + +export class E2BSandboxTerminal extends SandboxTerminal { + constructor(client: E2BClient) { + super(client); + } + + async command(input: SandboxTerminalCommandInput): Promise { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ); + } + + try { + const result = await this.client._sandbox.commands.run(input.command); + return { + output: result.stdout || result.stderr, + is_error: !!result.error || result.exitCode !== 0, + stdout: result.stdout, + stderr: result.stderr, + exit_code: result.exitCode, + }; + } catch (error) { + throw new ClientError( + ClientErrorCode.ImplementationError, + `Failed to execute command: ${error}`, + false, + ); + } + } + + async create(input: SandboxTerminalCreateInput): Promise { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ); + } + + const sandboxId = this.client._sandbox.sandboxId; + if (!sandboxId) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not running. Call start() or resume() first.', + false, + ); + } + + // Create a new terminal watcher for this terminal ID + if (!terminalWatchers.has(sandboxId)) { + terminalWatchers.set(sandboxId, new Map()); + } + if (!terminalWatchers.get(sandboxId)!.has(input.terminalId)) { + terminalWatchers + .get(sandboxId)! + .set(input.terminalId, new TerminalWatcher(this.client, input.terminalId)); + } + + terminalWatchers.get(sandboxId)!.get(input.terminalId)!.start(); + + return { + terminalId: input.terminalId, + name: input.name, + }; + } + + async open( + input: SandboxTerminalOpenInput, + onOutput: (output: SandboxTerminalOpenOutput) => void, + ) { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ); + } + + const watcher = terminalWatchers.get(this.client._sandbox.sandboxId)?.get(input.terminalId); + if (!watcher) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Terminal not found. Call create() first.', + false, + ); + } + + // Start the terminal watcher if it's not already running + if (watcher.off) { + await watcher.start(); + } + + watcher.onOutput((output) => onOutput(output)); + + return { + close: () => { + watcher.stop(); + }, + }; + } + + async write(input: SandboxTerminalWriteInput): Promise { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ); + } + + const watcher = terminalWatchers.get(this.client._sandbox.sandboxId)?.get(input.terminalId); + if (!watcher) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Terminal not found. Call create() first.', + false, + ); + } + + // Write input to the terminal + await watcher.writeInput(input.input); + + return { + output: '', + }; + } + + async run(input: SandboxTerminalRunInput): Promise { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ); + } + + const watcher = terminalWatchers.get(this.client._sandbox.sandboxId)?.get(input.terminalId); + if (!watcher) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Terminal not found. Call create() first.', + false, + ); + } + + // Execute the command and get output + await watcher.executeCommand(input.input); + + return { + output: '', + }; + } + + async kill(input: SandboxTerminalKillInput): Promise { + if (!this.client._sandbox) { + return { + output: 'Sandbox is not instantiated. Call start() or resume() first.', + }; + } + + const watcher = terminalWatchers.get(this.client._sandbox.sandboxId)?.get(input.terminalId); + if (watcher) { + await watcher.stop(); + terminalWatchers.get(this.client._sandbox.sandboxId)?.delete(input.terminalId); + } + + return { + output: 'Terminal killed', + }; + } +} + +class TerminalWatcher { + protected readonly watchTimeout = 0; + + protected _off: boolean = true; + protected _terminalHandle: CommandHandle | null = null; + protected _onOutputCallbacks: Array<(output: SandboxTerminalOpenOutput) => void> = []; + + constructor( + protected readonly client: E2BClient, + protected readonly terminalId: string, + ) {} + + get off(): boolean { + return this._off; + } + + async start(): Promise { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ); + } + + if (!this._off) { + return; + } + + this._off = false; + + // Create PTY terminal with real-time data callback + this._terminalHandle = await this.client._sandbox.pty.create({ + timeoutMs: this.watchTimeout, + onData: (data: Uint8Array) => { + // Convert Uint8Array to string + const output = new TextDecoder().decode(data); + this._onOutputCallbacks.forEach((callback) => callback({ id: uuid(), output })); + }, + cols: 80, + rows: 24, + }); + + try { + await this._terminalHandle?.wait(); + } catch (err) { + // taken from: https://github.com/e2b-dev/E2B/blob/main/packages/cli/src/terminal.ts#L10 + if (err instanceof CommandExitError) { + if (err.exitCode === -1 && err.error === 'signal: killed') { + return; + } + if (err.exitCode === 130) { + console.error('Terminal session was killed by user'); + return; + } + } + console.error('error waiting for terminal handle', err); + throw err; + } finally { + console.log('stopping terminal watcher'); + this.stop(); + } + } + + onOutput(callback: (output: SandboxTerminalOpenOutput) => void): void { + this._onOutputCallbacks.push(callback); + } + + async writeInput(input: string): Promise { + if (!this._terminalHandle) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Terminal not initialized. Call start() first.', + false, + ); + } + + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Terminal not initialized. Call start() first.', + false, + ); + } + + // Convert string to Uint8Array and send to the PTY terminal + const data = new TextEncoder().encode(input); + await this.client._sandbox.pty.sendInput(this._terminalHandle.pid, data); + } + + async executeCommand(command: string): Promise { + await this.writeInput(command); + } + + async stop(): Promise { + this._off = true; + + // Close the PTY terminal + if (this._terminalHandle) { + await this._terminalHandle.kill(); + this._terminalHandle = null; + } + + this._onOutputCallbacks = []; + } +} diff --git a/apps/coderouter/src/server.ts b/apps/coderouter/src/server.ts index 9080268751..07e27f3799 100644 --- a/apps/coderouter/src/server.ts +++ b/apps/coderouter/src/server.ts @@ -1,5 +1,4 @@ import 'dotenv/config'; -import { logger } from 'hono/logger'; import { OpenAPIHono } from '@hono/zod-openapi'; import { api_sandbox_create } from './api/sandbox/create'; import { api_sandbox_file_copy } from './api/sandbox/file/copy'; @@ -24,6 +23,13 @@ import { setupRequiredSandboxIdMiddleware } from './middleware/requireSandboxId' import { env, serve } from 'bun'; import { setupBeforeSandboxCallMiddleware } from './middleware/beforeSandboxCall'; import { setupErrorMiddleware } from './middleware/error'; +import { api_sandbox_terminal_create } from './api/sandbox/terminal/create'; +import { api_sandbox_terminal_command } from './api/sandbox/terminal/command'; +import { api_sandbox_terminal_open } from './api/sandbox/terminal/open'; +import { api_sandbox_terminal_run } from './api/sandbox/terminal/run'; +import { api_sandbox_terminal_write } from './api/sandbox/terminal/write'; +import { api_sandbox_terminal_kill } from './api/sandbox/terminal/kill'; +import { streamSSE } from 'hono/streaming'; export interface Variables { client: Client; @@ -63,6 +69,7 @@ setupBeforeSandboxCallMiddleware(app, basePath + '/sandbox/resume'); setupBeforeSandboxCallMiddleware(app, basePath + '/sandbox/stop'); setupBeforeSandboxCallMiddleware(app, basePath + '/sandbox/url'); setupBeforeSandboxCallMiddleware(app, basePath + '/sandbox/file/*'); +setupBeforeSandboxCallMiddleware(app, basePath + '/sandbox/terminal/*'); // auth routes api_auth_sign(app); @@ -85,6 +92,14 @@ api_sandbox_file_stat(app); api_sandbox_file_watch(app); api_sandbox_file_write(app); +// sandbox terminal routes +api_sandbox_terminal_command(app); +api_sandbox_terminal_create(app); +api_sandbox_terminal_open(app); +api_sandbox_terminal_run(app); +api_sandbox_terminal_write(app); +api_sandbox_terminal_kill(app); + app.doc('/openapi.json', { openapi: '3.0.0', info: { diff --git a/apps/nginx/conf.d/server.conf b/apps/nginx/conf.d/server.conf index 093a98602b..4e7c04e114 100644 --- a/apps/nginx/conf.d/server.conf +++ b/apps/nginx/conf.d/server.conf @@ -26,6 +26,17 @@ server { proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; + # to support SSE + location /coderouter/api/sandbox/terminal/open { + proxy_pass http://host.docker.internal:4444; + proxy_http_version 1.1; + proxy_set_header Connection ''; + chunked_transfer_encoding off; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 1h; # keep open longer than your expected SSE interval + } + location /coderouter { proxy_pass http://host.docker.internal:4444; } diff --git a/apps/web/client/src/components/store/editor/sandbox/index.ts b/apps/web/client/src/components/store/editor/sandbox/index.ts index 3d742380cc..3a2e54ad07 100644 --- a/apps/web/client/src/components/store/editor/sandbox/index.ts +++ b/apps/web/client/src/components/store/editor/sandbox/index.ts @@ -392,23 +392,23 @@ export class SandboxManager { // Convert ignored directories to glob patterns with ** wildcard const excludePatterns = EXCLUDED_SYNC_DIRECTORIES.map((dir) => `${dir}/**`); - const res = await this.session.provider.watchFiles({ - args: { - path: './', - recursive: true, - excludes: excludePatterns, - }, - onFileChange: async (event) => { - this.fileEventBus.publish({ - type: event.type, - paths: event.paths, - timestamp: Date.now(), - }); - await this.handleFileChange(event); - }, - }); - - this.fileWatcher = res.watcher; + // const res = await this.session.provider.watchFiles({ + // args: { + // path: './', + // recursive: true, + // excludes: excludePatterns, + // }, + // onFileChange: async (event) => { + // this.fileEventBus.publish({ + // type: event.type, + // paths: event.paths, + // timestamp: Date.now(), + // }); + // await this.handleFileChange(event); + // }, + // }); + + // this.fileWatcher = res.watcher; } async handleFileChange(event: WatchEvent) { diff --git a/bun.lock b/bun.lock index e35a75ba52..262cb9d22e 100644 --- a/bun.lock +++ b/bun.lock @@ -35,6 +35,7 @@ "jsonwebtoken": "^9.0.2", "mysql2": "^3.9.7", "pg": "^8.11.5", + "uuid": "^12.0.0", "zod": "^4.0.17", }, "devDependencies": { @@ -277,6 +278,7 @@ "version": "0.0.0", "dependencies": { "@codesandbox/sdk": "^1.1.6", + "@microsoft/fetch-event-source": "^2.0.1", "@onlook/models": "*", "@onlook/parser": "*", "@onlook/utility": "*", @@ -1164,6 +1166,8 @@ "@mendable/firecrawl-js": ["@mendable/firecrawl-js@1.29.3", "", { "dependencies": { "axios": "^1.11.0", "typescript-event-target": "^1.1.1", "zod": "^3.23.8", "zod-to-json-schema": "^3.23.0" } }, "sha512-+uvDktesJmVtiwxMtimq+3f5bKlsan4T7TokxOI7DbxFkApwrRNss5GYEXbInveMTz8LpGth/9Ch5BTwCqrpfA=="], + "@microsoft/fetch-event-source": ["@microsoft/fetch-event-source@2.0.1", "", {}, "sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA=="], + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="], "@nestjs/axios": ["@nestjs/axios@4.0.1", "", { "peerDependencies": { "@nestjs/common": "^10.0.0 || ^11.0.0", "axios": "^1.3.1", "rxjs": "^7.0.0" } }, "sha512-68pFJgu+/AZbWkGu65Z3r55bTsCPlgyKaV4BSG8yUAD72q1PPuyVRgUwFv6BxdnibTUHlyxm06FmYWNC+bjN7A=="], @@ -4894,6 +4898,8 @@ "coderouter/drizzle-orm": ["drizzle-orm@0.33.0", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=3", "@electric-sql/pglite": ">=0.1.1", "@libsql/client": "*", "@neondatabase/serverless": ">=0.1", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/react": ">=18", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=13.2.0", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "react": ">=18", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/react", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "knex", "kysely", "mysql2", "pg", "postgres", "react", "sql.js", "sqlite3"] }, "sha512-SHy72R2Rdkz0LEq0PSG/IdvnT3nGiWuRk+2tXZQ90GVq/XQhpCzu/EFT3V2rox+w8MlkBQxifF8pCStNYnERfA=="], + "coderouter/uuid": ["uuid@12.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-USe1zesMYh4fjCA8ZH5+X5WIVD0J4V1Jksm1bFTVBX2F/cwSXt0RO5w/3UXbdLKmZX65MiWV+hwhSS8p6oBTGA=="], + "coderouter/zod": ["zod@4.0.17", "", {}, "sha512-1PHjlYRevNxxdy2JZ8JcNAw7rX8V9P1AKkP+x/xZfxB0K5FYfuV+Ug6P/6NVSR2jHQ+FzDDoDHS04nYUsOIyLQ=="], "compression/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], diff --git a/packages/code-provider/package.json b/packages/code-provider/package.json index 37ca49cf98..9a2ca19098 100644 --- a/packages/code-provider/package.json +++ b/packages/code-provider/package.json @@ -34,6 +34,7 @@ }, "dependencies": { "@codesandbox/sdk": "^1.1.6", + "@microsoft/fetch-event-source": "^2.0.1", "@onlook/models": "*", "@onlook/parser": "*", "@onlook/utility": "*", diff --git a/packages/code-provider/src/providers/coderouter/codegen/.openapi-generator/FILES b/packages/code-provider/src/providers/coderouter/codegen/.openapi-generator/FILES index 05847e5220..e9de06b63c 100644 --- a/packages/code-provider/src/providers/coderouter/codegen/.openapi-generator/FILES +++ b/packages/code-provider/src/providers/coderouter/codegen/.openapi-generator/FILES @@ -22,6 +22,15 @@ models/CoderouterApiSandboxFileWatchPost200ResponseEventsInner.ts models/CoderouterApiSandboxFileWatchPostRequest.ts models/CoderouterApiSandboxFileWritePostRequest.ts models/CoderouterApiSandboxFileWritePostRequestFilesInner.ts +models/CoderouterApiSandboxTerminalCommandPost200Response.ts +models/CoderouterApiSandboxTerminalCommandPostRequest.ts +models/CoderouterApiSandboxTerminalCreatePostRequest.ts +models/CoderouterApiSandboxTerminalKillPost200Response.ts +models/CoderouterApiSandboxTerminalKillPostRequest.ts +models/CoderouterApiSandboxTerminalRunPost200Response.ts +models/CoderouterApiSandboxTerminalRunPostRequest.ts +models/CoderouterApiSandboxTerminalWritePost200Response.ts +models/CoderouterApiSandboxTerminalWritePostRequest.ts models/CoderouterApiSandboxUrlPost200Response.ts models/index.ts runtime.ts diff --git a/packages/code-provider/src/providers/coderouter/codegen/apis/DefaultApi.ts b/packages/code-provider/src/providers/coderouter/codegen/apis/DefaultApi.ts index e3a84936a3..84e7d00421 100644 --- a/packages/code-provider/src/providers/coderouter/codegen/apis/DefaultApi.ts +++ b/packages/code-provider/src/providers/coderouter/codegen/apis/DefaultApi.ts @@ -32,6 +32,15 @@ import type { CoderouterApiSandboxFileWatchPost200Response, CoderouterApiSandboxFileWatchPostRequest, CoderouterApiSandboxFileWritePostRequest, + CoderouterApiSandboxTerminalCommandPost200Response, + CoderouterApiSandboxTerminalCommandPostRequest, + CoderouterApiSandboxTerminalCreatePostRequest, + CoderouterApiSandboxTerminalKillPost200Response, + CoderouterApiSandboxTerminalKillPostRequest, + CoderouterApiSandboxTerminalRunPost200Response, + CoderouterApiSandboxTerminalRunPostRequest, + CoderouterApiSandboxTerminalWritePost200Response, + CoderouterApiSandboxTerminalWritePostRequest, CoderouterApiSandboxUrlPost200Response, } from '../models/index'; import { @@ -71,6 +80,24 @@ import { CoderouterApiSandboxFileWatchPostRequestToJSON, CoderouterApiSandboxFileWritePostRequestFromJSON, CoderouterApiSandboxFileWritePostRequestToJSON, + CoderouterApiSandboxTerminalCommandPost200ResponseFromJSON, + CoderouterApiSandboxTerminalCommandPost200ResponseToJSON, + CoderouterApiSandboxTerminalCommandPostRequestFromJSON, + CoderouterApiSandboxTerminalCommandPostRequestToJSON, + CoderouterApiSandboxTerminalCreatePostRequestFromJSON, + CoderouterApiSandboxTerminalCreatePostRequestToJSON, + CoderouterApiSandboxTerminalKillPost200ResponseFromJSON, + CoderouterApiSandboxTerminalKillPost200ResponseToJSON, + CoderouterApiSandboxTerminalKillPostRequestFromJSON, + CoderouterApiSandboxTerminalKillPostRequestToJSON, + CoderouterApiSandboxTerminalRunPost200ResponseFromJSON, + CoderouterApiSandboxTerminalRunPost200ResponseToJSON, + CoderouterApiSandboxTerminalRunPostRequestFromJSON, + CoderouterApiSandboxTerminalRunPostRequestToJSON, + CoderouterApiSandboxTerminalWritePost200ResponseFromJSON, + CoderouterApiSandboxTerminalWritePost200ResponseToJSON, + CoderouterApiSandboxTerminalWritePostRequestFromJSON, + CoderouterApiSandboxTerminalWritePostRequestToJSON, CoderouterApiSandboxUrlPost200ResponseFromJSON, CoderouterApiSandboxUrlPost200ResponseToJSON, } from '../models/index'; @@ -131,6 +158,30 @@ export interface CoderouterApiSandboxStopPostRequest { body?: object; } +export interface CoderouterApiSandboxTerminalCommandPostOperationRequest { + coderouterApiSandboxTerminalCommandPostRequest?: CoderouterApiSandboxTerminalCommandPostRequest; +} + +export interface CoderouterApiSandboxTerminalCreatePostOperationRequest { + coderouterApiSandboxTerminalCreatePostRequest?: CoderouterApiSandboxTerminalCreatePostRequest; +} + +export interface CoderouterApiSandboxTerminalKillPostOperationRequest { + coderouterApiSandboxTerminalKillPostRequest?: CoderouterApiSandboxTerminalKillPostRequest; +} + +export interface CoderouterApiSandboxTerminalOpenGetRequest { + terminalId: string; +} + +export interface CoderouterApiSandboxTerminalRunPostOperationRequest { + coderouterApiSandboxTerminalRunPostRequest?: CoderouterApiSandboxTerminalRunPostRequest; +} + +export interface CoderouterApiSandboxTerminalWritePostOperationRequest { + coderouterApiSandboxTerminalWritePostRequest?: CoderouterApiSandboxTerminalWritePostRequest; +} + /** * */ @@ -872,6 +923,338 @@ export class DefaultApi extends runtime.BaseAPI { return await response.value(); } + /** + */ + async coderouterApiSandboxTerminalCommandPostRaw( + requestParameters: CoderouterApiSandboxTerminalCommandPostOperationRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json'; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token('jwt', []); + + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } + } + + let urlPath = `/coderouter/api/sandbox/terminal/command`; + + const response = await this.request( + { + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: CoderouterApiSandboxTerminalCommandPostRequestToJSON( + requestParameters['coderouterApiSandboxTerminalCommandPostRequest'], + ), + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response, (jsonValue) => + CoderouterApiSandboxTerminalCommandPost200ResponseFromJSON(jsonValue), + ); + } + + /** + */ + async coderouterApiSandboxTerminalCommandPost( + requestParameters: CoderouterApiSandboxTerminalCommandPostOperationRequest = {}, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.coderouterApiSandboxTerminalCommandPostRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + + /** + */ + async coderouterApiSandboxTerminalCreatePostRaw( + requestParameters: CoderouterApiSandboxTerminalCreatePostOperationRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json'; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token('jwt', []); + + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } + } + + let urlPath = `/coderouter/api/sandbox/terminal/create`; + + const response = await this.request( + { + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: CoderouterApiSandboxTerminalCreatePostRequestToJSON( + requestParameters['coderouterApiSandboxTerminalCreatePostRequest'], + ), + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response, (jsonValue) => + CoderouterApiSandboxTerminalCreatePostRequestFromJSON(jsonValue), + ); + } + + /** + */ + async coderouterApiSandboxTerminalCreatePost( + requestParameters: CoderouterApiSandboxTerminalCreatePostOperationRequest = {}, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.coderouterApiSandboxTerminalCreatePostRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + + /** + */ + async coderouterApiSandboxTerminalKillPostRaw( + requestParameters: CoderouterApiSandboxTerminalKillPostOperationRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json'; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token('jwt', []); + + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } + } + + let urlPath = `/coderouter/api/sandbox/terminal/kill`; + + const response = await this.request( + { + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: CoderouterApiSandboxTerminalKillPostRequestToJSON( + requestParameters['coderouterApiSandboxTerminalKillPostRequest'], + ), + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response, (jsonValue) => + CoderouterApiSandboxTerminalKillPost200ResponseFromJSON(jsonValue), + ); + } + + /** + */ + async coderouterApiSandboxTerminalKillPost( + requestParameters: CoderouterApiSandboxTerminalKillPostOperationRequest = {}, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.coderouterApiSandboxTerminalKillPostRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + + /** + */ + async coderouterApiSandboxTerminalOpenGetRaw( + requestParameters: CoderouterApiSandboxTerminalOpenGetRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + if (requestParameters['terminalId'] == null) { + throw new runtime.RequiredError( + 'terminalId', + 'Required parameter "terminalId" was null or undefined when calling coderouterApiSandboxTerminalOpenGet().', + ); + } + + const queryParameters: any = {}; + + if (requestParameters['terminalId'] != null) { + queryParameters['terminalId'] = requestParameters['terminalId']; + } + + const headerParameters: runtime.HTTPHeaders = {}; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token('jwt', []); + + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } + } + + let urlPath = `/coderouter/api/sandbox/terminal/open`; + + const response = await this.request( + { + path: urlPath, + method: 'GET', + headers: headerParameters, + query: queryParameters, + }, + initOverrides, + ); + + if (this.isJsonMime(response.headers.get('content-type'))) { + return new runtime.JSONApiResponse(response); + } else { + return new runtime.TextApiResponse(response) as any; + } + } + + /** + */ + async coderouterApiSandboxTerminalOpenGet( + requestParameters: CoderouterApiSandboxTerminalOpenGetRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.coderouterApiSandboxTerminalOpenGetRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + + /** + */ + async coderouterApiSandboxTerminalRunPostRaw( + requestParameters: CoderouterApiSandboxTerminalRunPostOperationRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json'; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token('jwt', []); + + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } + } + + let urlPath = `/coderouter/api/sandbox/terminal/run`; + + const response = await this.request( + { + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: CoderouterApiSandboxTerminalRunPostRequestToJSON( + requestParameters['coderouterApiSandboxTerminalRunPostRequest'], + ), + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response, (jsonValue) => + CoderouterApiSandboxTerminalRunPost200ResponseFromJSON(jsonValue), + ); + } + + /** + */ + async coderouterApiSandboxTerminalRunPost( + requestParameters: CoderouterApiSandboxTerminalRunPostOperationRequest = {}, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.coderouterApiSandboxTerminalRunPostRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + + /** + */ + async coderouterApiSandboxTerminalWritePostRaw( + requestParameters: CoderouterApiSandboxTerminalWritePostOperationRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json'; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token('jwt', []); + + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } + } + + let urlPath = `/coderouter/api/sandbox/terminal/write`; + + const response = await this.request( + { + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: CoderouterApiSandboxTerminalWritePostRequestToJSON( + requestParameters['coderouterApiSandboxTerminalWritePostRequest'], + ), + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response, (jsonValue) => + CoderouterApiSandboxTerminalWritePost200ResponseFromJSON(jsonValue), + ); + } + + /** + */ + async coderouterApiSandboxTerminalWritePost( + requestParameters: CoderouterApiSandboxTerminalWritePostOperationRequest = {}, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.coderouterApiSandboxTerminalWritePostRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + /** */ async coderouterApiSandboxUrlPostRaw( diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalCommandPost200Response.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalCommandPost200Response.ts new file mode 100644 index 0000000000..814ef7036b --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalCommandPost200Response.ts @@ -0,0 +1,111 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface CoderouterApiSandboxTerminalCommandPost200Response + */ +export interface CoderouterApiSandboxTerminalCommandPost200Response { + /** + * Whether the command was successful. + * @type {boolean} + * @memberof CoderouterApiSandboxTerminalCommandPost200Response + */ + isError: boolean; + /** + * The output of the command. + * @type {string} + * @memberof CoderouterApiSandboxTerminalCommandPost200Response + */ + output: string; + /** + * The stdout of the command. + * @type {string} + * @memberof CoderouterApiSandboxTerminalCommandPost200Response + */ + stdout: string; + /** + * The stderr of the command. + * @type {string} + * @memberof CoderouterApiSandboxTerminalCommandPost200Response + */ + stderr: string; + /** + * The exit code of the command. + * @type {number} + * @memberof CoderouterApiSandboxTerminalCommandPost200Response + */ + exitCode: number; +} + +/** + * Check if a given object implements the CoderouterApiSandboxTerminalCommandPost200Response interface. + */ +export function instanceOfCoderouterApiSandboxTerminalCommandPost200Response( + value: object, +): value is CoderouterApiSandboxTerminalCommandPost200Response { + if (!('isError' in value) || value['isError'] === undefined) return false; + if (!('output' in value) || value['output'] === undefined) return false; + if (!('stdout' in value) || value['stdout'] === undefined) return false; + if (!('stderr' in value) || value['stderr'] === undefined) return false; + if (!('exitCode' in value) || value['exitCode'] === undefined) return false; + return true; +} + +export function CoderouterApiSandboxTerminalCommandPost200ResponseFromJSON( + json: any, +): CoderouterApiSandboxTerminalCommandPost200Response { + return CoderouterApiSandboxTerminalCommandPost200ResponseFromJSONTyped(json, false); +} + +export function CoderouterApiSandboxTerminalCommandPost200ResponseFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiSandboxTerminalCommandPost200Response { + if (json == null) { + return json; + } + return { + isError: json['is_error'], + output: json['output'], + stdout: json['stdout'], + stderr: json['stderr'], + exitCode: json['exit_code'], + }; +} + +export function CoderouterApiSandboxTerminalCommandPost200ResponseToJSON( + json: any, +): CoderouterApiSandboxTerminalCommandPost200Response { + return CoderouterApiSandboxTerminalCommandPost200ResponseToJSONTyped(json, false); +} + +export function CoderouterApiSandboxTerminalCommandPost200ResponseToJSONTyped( + value?: CoderouterApiSandboxTerminalCommandPost200Response | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + is_error: value['isError'], + output: value['output'], + stdout: value['stdout'], + stderr: value['stderr'], + exit_code: value['exitCode'], + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalCommandPostRequest.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalCommandPostRequest.ts new file mode 100644 index 0000000000..b5fc3e54d1 --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalCommandPostRequest.ts @@ -0,0 +1,75 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface CoderouterApiSandboxTerminalCommandPostRequest + */ +export interface CoderouterApiSandboxTerminalCommandPostRequest { + /** + * The command to run. + * @type {string} + * @memberof CoderouterApiSandboxTerminalCommandPostRequest + */ + command: string; +} + +/** + * Check if a given object implements the CoderouterApiSandboxTerminalCommandPostRequest interface. + */ +export function instanceOfCoderouterApiSandboxTerminalCommandPostRequest( + value: object, +): value is CoderouterApiSandboxTerminalCommandPostRequest { + if (!('command' in value) || value['command'] === undefined) return false; + return true; +} + +export function CoderouterApiSandboxTerminalCommandPostRequestFromJSON( + json: any, +): CoderouterApiSandboxTerminalCommandPostRequest { + return CoderouterApiSandboxTerminalCommandPostRequestFromJSONTyped(json, false); +} + +export function CoderouterApiSandboxTerminalCommandPostRequestFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiSandboxTerminalCommandPostRequest { + if (json == null) { + return json; + } + return { + command: json['command'], + }; +} + +export function CoderouterApiSandboxTerminalCommandPostRequestToJSON( + json: any, +): CoderouterApiSandboxTerminalCommandPostRequest { + return CoderouterApiSandboxTerminalCommandPostRequestToJSONTyped(json, false); +} + +export function CoderouterApiSandboxTerminalCommandPostRequestToJSONTyped( + value?: CoderouterApiSandboxTerminalCommandPostRequest | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + command: value['command'], + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalCreatePostRequest.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalCreatePostRequest.ts new file mode 100644 index 0000000000..a7a4434c7f --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalCreatePostRequest.ts @@ -0,0 +1,84 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface CoderouterApiSandboxTerminalCreatePostRequest + */ +export interface CoderouterApiSandboxTerminalCreatePostRequest { + /** + * The ID of the terminal. + * @type {string} + * @memberof CoderouterApiSandboxTerminalCreatePostRequest + */ + terminalId: string; + /** + * The name of the terminal. + * @type {string} + * @memberof CoderouterApiSandboxTerminalCreatePostRequest + */ + name: string; +} + +/** + * Check if a given object implements the CoderouterApiSandboxTerminalCreatePostRequest interface. + */ +export function instanceOfCoderouterApiSandboxTerminalCreatePostRequest( + value: object, +): value is CoderouterApiSandboxTerminalCreatePostRequest { + if (!('terminalId' in value) || value['terminalId'] === undefined) return false; + if (!('name' in value) || value['name'] === undefined) return false; + return true; +} + +export function CoderouterApiSandboxTerminalCreatePostRequestFromJSON( + json: any, +): CoderouterApiSandboxTerminalCreatePostRequest { + return CoderouterApiSandboxTerminalCreatePostRequestFromJSONTyped(json, false); +} + +export function CoderouterApiSandboxTerminalCreatePostRequestFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiSandboxTerminalCreatePostRequest { + if (json == null) { + return json; + } + return { + terminalId: json['terminalId'], + name: json['name'], + }; +} + +export function CoderouterApiSandboxTerminalCreatePostRequestToJSON( + json: any, +): CoderouterApiSandboxTerminalCreatePostRequest { + return CoderouterApiSandboxTerminalCreatePostRequestToJSONTyped(json, false); +} + +export function CoderouterApiSandboxTerminalCreatePostRequestToJSONTyped( + value?: CoderouterApiSandboxTerminalCreatePostRequest | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + terminalId: value['terminalId'], + name: value['name'], + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalKillPost200Response.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalKillPost200Response.ts new file mode 100644 index 0000000000..12237da659 --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalKillPost200Response.ts @@ -0,0 +1,75 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface CoderouterApiSandboxTerminalKillPost200Response + */ +export interface CoderouterApiSandboxTerminalKillPost200Response { + /** + * The output of the kill command. + * @type {string} + * @memberof CoderouterApiSandboxTerminalKillPost200Response + */ + output: string; +} + +/** + * Check if a given object implements the CoderouterApiSandboxTerminalKillPost200Response interface. + */ +export function instanceOfCoderouterApiSandboxTerminalKillPost200Response( + value: object, +): value is CoderouterApiSandboxTerminalKillPost200Response { + if (!('output' in value) || value['output'] === undefined) return false; + return true; +} + +export function CoderouterApiSandboxTerminalKillPost200ResponseFromJSON( + json: any, +): CoderouterApiSandboxTerminalKillPost200Response { + return CoderouterApiSandboxTerminalKillPost200ResponseFromJSONTyped(json, false); +} + +export function CoderouterApiSandboxTerminalKillPost200ResponseFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiSandboxTerminalKillPost200Response { + if (json == null) { + return json; + } + return { + output: json['output'], + }; +} + +export function CoderouterApiSandboxTerminalKillPost200ResponseToJSON( + json: any, +): CoderouterApiSandboxTerminalKillPost200Response { + return CoderouterApiSandboxTerminalKillPost200ResponseToJSONTyped(json, false); +} + +export function CoderouterApiSandboxTerminalKillPost200ResponseToJSONTyped( + value?: CoderouterApiSandboxTerminalKillPost200Response | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + output: value['output'], + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalKillPostRequest.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalKillPostRequest.ts new file mode 100644 index 0000000000..c4d31f5dbc --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalKillPostRequest.ts @@ -0,0 +1,75 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface CoderouterApiSandboxTerminalKillPostRequest + */ +export interface CoderouterApiSandboxTerminalKillPostRequest { + /** + * The ID of the terminal. + * @type {string} + * @memberof CoderouterApiSandboxTerminalKillPostRequest + */ + terminalId: string; +} + +/** + * Check if a given object implements the CoderouterApiSandboxTerminalKillPostRequest interface. + */ +export function instanceOfCoderouterApiSandboxTerminalKillPostRequest( + value: object, +): value is CoderouterApiSandboxTerminalKillPostRequest { + if (!('terminalId' in value) || value['terminalId'] === undefined) return false; + return true; +} + +export function CoderouterApiSandboxTerminalKillPostRequestFromJSON( + json: any, +): CoderouterApiSandboxTerminalKillPostRequest { + return CoderouterApiSandboxTerminalKillPostRequestFromJSONTyped(json, false); +} + +export function CoderouterApiSandboxTerminalKillPostRequestFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiSandboxTerminalKillPostRequest { + if (json == null) { + return json; + } + return { + terminalId: json['terminalId'], + }; +} + +export function CoderouterApiSandboxTerminalKillPostRequestToJSON( + json: any, +): CoderouterApiSandboxTerminalKillPostRequest { + return CoderouterApiSandboxTerminalKillPostRequestToJSONTyped(json, false); +} + +export function CoderouterApiSandboxTerminalKillPostRequestToJSONTyped( + value?: CoderouterApiSandboxTerminalKillPostRequest | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + terminalId: value['terminalId'], + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalOpenPost200Response.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalOpenPost200Response.ts new file mode 100644 index 0000000000..3e1fd9e458 --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalOpenPost200Response.ts @@ -0,0 +1,75 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface CoderouterApiSandboxTerminalOpenPost200Response + */ +export interface CoderouterApiSandboxTerminalOpenPost200Response { + /** + * The output of the open command. + * @type {string} + * @memberof CoderouterApiSandboxTerminalOpenPost200Response + */ + output: string; +} + +/** + * Check if a given object implements the CoderouterApiSandboxTerminalOpenPost200Response interface. + */ +export function instanceOfCoderouterApiSandboxTerminalOpenPost200Response( + value: object, +): value is CoderouterApiSandboxTerminalOpenPost200Response { + if (!('output' in value) || value['output'] === undefined) return false; + return true; +} + +export function CoderouterApiSandboxTerminalOpenPost200ResponseFromJSON( + json: any, +): CoderouterApiSandboxTerminalOpenPost200Response { + return CoderouterApiSandboxTerminalOpenPost200ResponseFromJSONTyped(json, false); +} + +export function CoderouterApiSandboxTerminalOpenPost200ResponseFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiSandboxTerminalOpenPost200Response { + if (json == null) { + return json; + } + return { + output: json['output'], + }; +} + +export function CoderouterApiSandboxTerminalOpenPost200ResponseToJSON( + json: any, +): CoderouterApiSandboxTerminalOpenPost200Response { + return CoderouterApiSandboxTerminalOpenPost200ResponseToJSONTyped(json, false); +} + +export function CoderouterApiSandboxTerminalOpenPost200ResponseToJSONTyped( + value?: CoderouterApiSandboxTerminalOpenPost200Response | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + output: value['output'], + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalOpenPostRequest.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalOpenPostRequest.ts new file mode 100644 index 0000000000..41b81fbd30 --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalOpenPostRequest.ts @@ -0,0 +1,75 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface CoderouterApiSandboxTerminalOpenPostRequest + */ +export interface CoderouterApiSandboxTerminalOpenPostRequest { + /** + * The ID of the terminal. + * @type {string} + * @memberof CoderouterApiSandboxTerminalOpenPostRequest + */ + terminalId: string; +} + +/** + * Check if a given object implements the CoderouterApiSandboxTerminalOpenPostRequest interface. + */ +export function instanceOfCoderouterApiSandboxTerminalOpenPostRequest( + value: object, +): value is CoderouterApiSandboxTerminalOpenPostRequest { + if (!('terminalId' in value) || value['terminalId'] === undefined) return false; + return true; +} + +export function CoderouterApiSandboxTerminalOpenPostRequestFromJSON( + json: any, +): CoderouterApiSandboxTerminalOpenPostRequest { + return CoderouterApiSandboxTerminalOpenPostRequestFromJSONTyped(json, false); +} + +export function CoderouterApiSandboxTerminalOpenPostRequestFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiSandboxTerminalOpenPostRequest { + if (json == null) { + return json; + } + return { + terminalId: json['terminalId'], + }; +} + +export function CoderouterApiSandboxTerminalOpenPostRequestToJSON( + json: any, +): CoderouterApiSandboxTerminalOpenPostRequest { + return CoderouterApiSandboxTerminalOpenPostRequestToJSONTyped(json, false); +} + +export function CoderouterApiSandboxTerminalOpenPostRequestToJSONTyped( + value?: CoderouterApiSandboxTerminalOpenPostRequest | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + terminalId: value['terminalId'], + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalRunPost200Response.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalRunPost200Response.ts new file mode 100644 index 0000000000..d36280975b --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalRunPost200Response.ts @@ -0,0 +1,75 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface CoderouterApiSandboxTerminalRunPost200Response + */ +export interface CoderouterApiSandboxTerminalRunPost200Response { + /** + * The output of the run command. The output includes lines before and after the command execution. + * @type {string} + * @memberof CoderouterApiSandboxTerminalRunPost200Response + */ + output: string; +} + +/** + * Check if a given object implements the CoderouterApiSandboxTerminalRunPost200Response interface. + */ +export function instanceOfCoderouterApiSandboxTerminalRunPost200Response( + value: object, +): value is CoderouterApiSandboxTerminalRunPost200Response { + if (!('output' in value) || value['output'] === undefined) return false; + return true; +} + +export function CoderouterApiSandboxTerminalRunPost200ResponseFromJSON( + json: any, +): CoderouterApiSandboxTerminalRunPost200Response { + return CoderouterApiSandboxTerminalRunPost200ResponseFromJSONTyped(json, false); +} + +export function CoderouterApiSandboxTerminalRunPost200ResponseFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiSandboxTerminalRunPost200Response { + if (json == null) { + return json; + } + return { + output: json['output'], + }; +} + +export function CoderouterApiSandboxTerminalRunPost200ResponseToJSON( + json: any, +): CoderouterApiSandboxTerminalRunPost200Response { + return CoderouterApiSandboxTerminalRunPost200ResponseToJSONTyped(json, false); +} + +export function CoderouterApiSandboxTerminalRunPost200ResponseToJSONTyped( + value?: CoderouterApiSandboxTerminalRunPost200Response | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + output: value['output'], + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalRunPostRequest.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalRunPostRequest.ts new file mode 100644 index 0000000000..073206653b --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalRunPostRequest.ts @@ -0,0 +1,84 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface CoderouterApiSandboxTerminalRunPostRequest + */ +export interface CoderouterApiSandboxTerminalRunPostRequest { + /** + * The ID of the terminal. + * @type {string} + * @memberof CoderouterApiSandboxTerminalRunPostRequest + */ + terminalId: string; + /** + * The name of the terminal. + * @type {string} + * @memberof CoderouterApiSandboxTerminalRunPostRequest + */ + input: string; +} + +/** + * Check if a given object implements the CoderouterApiSandboxTerminalRunPostRequest interface. + */ +export function instanceOfCoderouterApiSandboxTerminalRunPostRequest( + value: object, +): value is CoderouterApiSandboxTerminalRunPostRequest { + if (!('terminalId' in value) || value['terminalId'] === undefined) return false; + if (!('input' in value) || value['input'] === undefined) return false; + return true; +} + +export function CoderouterApiSandboxTerminalRunPostRequestFromJSON( + json: any, +): CoderouterApiSandboxTerminalRunPostRequest { + return CoderouterApiSandboxTerminalRunPostRequestFromJSONTyped(json, false); +} + +export function CoderouterApiSandboxTerminalRunPostRequestFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiSandboxTerminalRunPostRequest { + if (json == null) { + return json; + } + return { + terminalId: json['terminalId'], + input: json['input'], + }; +} + +export function CoderouterApiSandboxTerminalRunPostRequestToJSON( + json: any, +): CoderouterApiSandboxTerminalRunPostRequest { + return CoderouterApiSandboxTerminalRunPostRequestToJSONTyped(json, false); +} + +export function CoderouterApiSandboxTerminalRunPostRequestToJSONTyped( + value?: CoderouterApiSandboxTerminalRunPostRequest | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + terminalId: value['terminalId'], + input: value['input'], + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalWritePost200Response.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalWritePost200Response.ts new file mode 100644 index 0000000000..c89b9fb6b8 --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalWritePost200Response.ts @@ -0,0 +1,75 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface CoderouterApiSandboxTerminalWritePost200Response + */ +export interface CoderouterApiSandboxTerminalWritePost200Response { + /** + * The output of the write command. + * @type {string} + * @memberof CoderouterApiSandboxTerminalWritePost200Response + */ + output: string; +} + +/** + * Check if a given object implements the CoderouterApiSandboxTerminalWritePost200Response interface. + */ +export function instanceOfCoderouterApiSandboxTerminalWritePost200Response( + value: object, +): value is CoderouterApiSandboxTerminalWritePost200Response { + if (!('output' in value) || value['output'] === undefined) return false; + return true; +} + +export function CoderouterApiSandboxTerminalWritePost200ResponseFromJSON( + json: any, +): CoderouterApiSandboxTerminalWritePost200Response { + return CoderouterApiSandboxTerminalWritePost200ResponseFromJSONTyped(json, false); +} + +export function CoderouterApiSandboxTerminalWritePost200ResponseFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiSandboxTerminalWritePost200Response { + if (json == null) { + return json; + } + return { + output: json['output'], + }; +} + +export function CoderouterApiSandboxTerminalWritePost200ResponseToJSON( + json: any, +): CoderouterApiSandboxTerminalWritePost200Response { + return CoderouterApiSandboxTerminalWritePost200ResponseToJSONTyped(json, false); +} + +export function CoderouterApiSandboxTerminalWritePost200ResponseToJSONTyped( + value?: CoderouterApiSandboxTerminalWritePost200Response | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + output: value['output'], + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalWritePostRequest.ts b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalWritePostRequest.ts new file mode 100644 index 0000000000..a165db22fb --- /dev/null +++ b/packages/code-provider/src/providers/coderouter/codegen/models/CoderouterApiSandboxTerminalWritePostRequest.ts @@ -0,0 +1,84 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Coderouter + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface CoderouterApiSandboxTerminalWritePostRequest + */ +export interface CoderouterApiSandboxTerminalWritePostRequest { + /** + * The ID of the terminal. + * @type {string} + * @memberof CoderouterApiSandboxTerminalWritePostRequest + */ + terminalId: string; + /** + * The input to the write command. + * @type {string} + * @memberof CoderouterApiSandboxTerminalWritePostRequest + */ + input: string; +} + +/** + * Check if a given object implements the CoderouterApiSandboxTerminalWritePostRequest interface. + */ +export function instanceOfCoderouterApiSandboxTerminalWritePostRequest( + value: object, +): value is CoderouterApiSandboxTerminalWritePostRequest { + if (!('terminalId' in value) || value['terminalId'] === undefined) return false; + if (!('input' in value) || value['input'] === undefined) return false; + return true; +} + +export function CoderouterApiSandboxTerminalWritePostRequestFromJSON( + json: any, +): CoderouterApiSandboxTerminalWritePostRequest { + return CoderouterApiSandboxTerminalWritePostRequestFromJSONTyped(json, false); +} + +export function CoderouterApiSandboxTerminalWritePostRequestFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CoderouterApiSandboxTerminalWritePostRequest { + if (json == null) { + return json; + } + return { + terminalId: json['terminalId'], + input: json['input'], + }; +} + +export function CoderouterApiSandboxTerminalWritePostRequestToJSON( + json: any, +): CoderouterApiSandboxTerminalWritePostRequest { + return CoderouterApiSandboxTerminalWritePostRequestToJSONTyped(json, false); +} + +export function CoderouterApiSandboxTerminalWritePostRequestToJSONTyped( + value?: CoderouterApiSandboxTerminalWritePostRequest | null, + ignoreDiscriminator: boolean = false, +): any { + if (value == null) { + return value; + } + + return { + terminalId: value['terminalId'], + input: value['input'], + }; +} diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/index.ts b/packages/code-provider/src/providers/coderouter/codegen/models/index.ts index 6cbf9d1106..98e058b7ae 100644 --- a/packages/code-provider/src/providers/coderouter/codegen/models/index.ts +++ b/packages/code-provider/src/providers/coderouter/codegen/models/index.ts @@ -21,4 +21,13 @@ export * from './CoderouterApiSandboxFileWatchPost200ResponseEventsInner'; export * from './CoderouterApiSandboxFileWatchPostRequest'; export * from './CoderouterApiSandboxFileWritePostRequest'; export * from './CoderouterApiSandboxFileWritePostRequestFilesInner'; +export * from './CoderouterApiSandboxTerminalCommandPost200Response'; +export * from './CoderouterApiSandboxTerminalCommandPostRequest'; +export * from './CoderouterApiSandboxTerminalCreatePostRequest'; +export * from './CoderouterApiSandboxTerminalKillPost200Response'; +export * from './CoderouterApiSandboxTerminalKillPostRequest'; +export * from './CoderouterApiSandboxTerminalRunPost200Response'; +export * from './CoderouterApiSandboxTerminalRunPostRequest'; +export * from './CoderouterApiSandboxTerminalWritePost200Response'; +export * from './CoderouterApiSandboxTerminalWritePostRequest'; export * from './CoderouterApiSandboxUrlPost200Response'; diff --git a/packages/code-provider/src/providers/coderouter/index.ts b/packages/code-provider/src/providers/coderouter/index.ts index 3f1138e237..d30e7ee584 100644 --- a/packages/code-provider/src/providers/coderouter/index.ts +++ b/packages/code-provider/src/providers/coderouter/index.ts @@ -52,6 +52,8 @@ import { } from '../../types'; import * as OpenAPI from './codegen'; +import { v4 as uuid } from 'uuid'; +import { fetchEventSource } from '@microsoft/fetch-event-source'; export interface CoderouterProviderOptions { // URL to Coderouter @@ -272,8 +274,10 @@ export class CoderouterProvider extends Provider { } async createTerminal(input: CreateTerminalInput): Promise { + const terminal = new CoderouterTerminal(this.api, () => this.requestInitOverrides()); + await terminal.start(); return { - terminal: new CoderouterTerminal(), + terminal, }; } @@ -474,32 +478,149 @@ export class CoderouterFileWatcher extends ProviderFileWatcher { } export class CoderouterTerminal extends ProviderTerminal { + protected _id: string; + protected callbacks: Array<(output: string) => void> = []; + protected abortController: AbortController | null = null; + + constructor( + private readonly api: OpenAPI.DefaultApi, + private readonly requestInitOverrides: () => RequestInit, + ) { + super(); + this._id = uuid(); + } + get id(): string { - return 'unimplemented'; + return this._id; } get name(): string { - return 'unimplemented'; + return this._id; } - open(): Promise { - return Promise.resolve(''); + protected off = true; + + async start(): Promise { + if (!this.off) { + return; + } + + this.off = false; + + await this.api.coderouterApiSandboxTerminalCreatePost( + { + coderouterApiSandboxTerminalCreatePostRequest: { + terminalId: this._id, + name: this._id, + }, + }, + this.requestInitOverrides(), + ); } - write(): Promise { - return Promise.resolve(); + async open(dimensions?: object): Promise { + if (this.off) { + return ''; + } + + fetchEventSource( + `${this.api['configuration'].basePath}/coderouter/api/sandbox/terminal/open?terminalId=${this._id}`, + { + ...(this.requestInitOverrides() as Record), + onmessage: (event) => { + if (event.event === 'message') { + const res = JSON.parse(event.data); + console.log('res', res); + this.callbacks.forEach((callback) => callback(res.output)); + } + }, + onerror: (err) => { + console.error('[coderouter/terminal/open] SSE error', err); + }, + }, + ); + + // this.off = false; + + // const tick = async () => { + // if (this.off) { + // return ''; + // } + + // if (this.abortController) { + // this.abortController.abort(); + // } + + // this.abortController = new AbortController(); + + // let output = ''; + // try { + // const body = await this.api.coderouterApiSandboxTerminalOpenGet( + // { + // terminalId: this._id, + // }, + // { + // ...this.requestInitOverrides(), + // signal: this.abortController?.signal, + // }, + // ); + + // this.callbacks.forEach((callback) => { + // callback(body.output); + // }); + + // output = body.output; + // } catch (err) { + // console.error('[coderouter] poll error', err); + // } finally { + // // schedule next poll immediately + // setTimeout(tick, 200); + // } + // return output; + // }; + + // tick(); + return ''; + } + + async write(input: string, dimensions?: object): Promise { + await this.api.coderouterApiSandboxTerminalWritePost( + { + coderouterApiSandboxTerminalWritePostRequest: { + terminalId: this._id, + input, + }, + }, + this.requestInitOverrides(), + ); } - run(): Promise { - return Promise.resolve(); + async run(input: string, dimensions?: object): Promise { + await this.api.coderouterApiSandboxTerminalRunPost( + { + coderouterApiSandboxTerminalRunPostRequest: { + terminalId: this._id, + input, + }, + }, + this.requestInitOverrides(), + ); } - kill(): Promise { - return Promise.resolve(); + async kill(): Promise { + await this.api.coderouterApiSandboxTerminalKillPost( + { + coderouterApiSandboxTerminalKillPostRequest: { + terminalId: this._id, + }, + }, + this.requestInitOverrides(), + ); } onOutput(callback: (data: string) => void): () => void { - return () => {}; + this.callbacks.push(callback); + return () => null; } } From 31467ffc156ddd2bede94ee10432c7670497c8d4 Mon Sep 17 00:00:00 2001 From: Thomas Potaire Date: Mon, 8 Sep 2025 15:37:21 -0700 Subject: [PATCH 4/5] addressing ai comments, bit of polish, refactoring watch to use sse, upgrading e2b sdk to allow pausing --- apps/coderouter/bun.lock | 669 ++++++++++++++++++ apps/coderouter/package.json | 2 +- .../src/api/sandbox/file/watch/index.ts | 56 +- .../src/api/sandbox/terminal/command/index.ts | 18 +- .../src/api/sandbox/terminal/open/index.ts | 10 +- .../src/api/sandbox/terminal/run/index.ts | 4 +- .../src/middleware/beforeSandboxCall.ts | 22 +- .../provider/definition/sandbox/file/index.ts | 6 +- .../definition/sandbox/terminal/index.ts | 8 +- .../src/provider/e2b/sandbox/file/index.ts | 61 +- .../src/provider/e2b/sandbox/index.ts | 26 +- .../provider/e2b/sandbox/terminal/index.ts | 20 +- .../components/store/editor/sandbox/index.ts | 34 +- bun.lock | 12 +- .../codegen/.openapi-generator/FILES | 2 - .../coderouter/codegen/apis/DefaultApi.ts | 15 +- .../coderouter/codegen/models/index.ts | 2 - .../src/providers/coderouter/index.ts | 145 ++-- 18 files changed, 871 insertions(+), 241 deletions(-) create mode 100644 apps/coderouter/bun.lock diff --git a/apps/coderouter/bun.lock b/apps/coderouter/bun.lock new file mode 100644 index 0000000000..4d3f68d91b --- /dev/null +++ b/apps/coderouter/bun.lock @@ -0,0 +1,669 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "coderouter", + "dependencies": { + "@e2b/code-interpreter": "2.0.0", + "@hono/zod-openapi": "^1.1.0", + "@hono/zod-validator": "^0.2.1", + "@types/jsonwebtoken": "^9.0.10", + "better-sqlite3": "^9.4.3", + "dotenv": "^16.4.5", + "drizzle-kit": "^0.27.0", + "drizzle-orm": "^0.33.0", + "hono": "^4.9.2", + "jsonwebtoken": "^9.0.2", + "mysql2": "^3.9.7", + "pg": "^8.11.5", + "uuid": "^12.0.0", + "zod": "^4.0.17", + }, + "devDependencies": { + "@biomejs/biome": "^2.1.4", + "@types/node": "^22.5.0", + "bun-types": "^1.2.20", + "supertest": "^7.0.0", + "typedoc": "^0.26.6", + "undici": "^6.19.8", + }, + }, + }, + "packages": { + "@asteasolutions/zod-to-openapi": ["@asteasolutions/zod-to-openapi@8.1.0", "", { "dependencies": { "openapi3-ts": "^4.1.2" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-tQFxVs05J/6QXXqIzj6rTRk3nj1HFs4pe+uThwE95jL5II2JfpVXkK+CqkO7aT0Do5AYqO6LDrKpleLUFXgY+g=="], + + "@biomejs/biome": ["@biomejs/biome@2.2.3", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.2.3", "@biomejs/cli-darwin-x64": "2.2.3", "@biomejs/cli-linux-arm64": "2.2.3", "@biomejs/cli-linux-arm64-musl": "2.2.3", "@biomejs/cli-linux-x64": "2.2.3", "@biomejs/cli-linux-x64-musl": "2.2.3", "@biomejs/cli-win32-arm64": "2.2.3", "@biomejs/cli-win32-x64": "2.2.3" }, "bin": { "biome": "bin/biome" } }, "sha512-9w0uMTvPrIdvUrxazZ42Ib7t8Y2yoGLKLdNne93RLICmaHw7mcLv4PPb5LvZLJF3141gQHiCColOh/v6VWlWmg=="], + + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.2.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-OrqQVBpadB5eqzinXN4+Q6honBz+tTlKVCsbEuEpljK8ASSItzIRZUA02mTikl3H/1nO2BMPFiJ0nkEZNy3B1w=="], + + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.2.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-OCdBpb1TmyfsTgBAM1kPMXyYKTohQ48WpiN9tkt9xvU6gKVKHY4oVwteBebiOqyfyzCNaSiuKIPjmHjUZ2ZNMg=="], + + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.2.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-g/Uta2DqYpECxG+vUmTAmUKlVhnGEcY7DXWgKP8ruLRa8Si1QHsWknPY3B/wCo0KgYiFIOAZ9hjsHfNb9L85+g=="], + + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.2.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-q3w9jJ6JFPZPeqyvwwPeaiS/6NEszZ+pXKF+IczNo8Xj6fsii45a4gEEicKyKIytalV+s829ACZujQlXAiVLBQ=="], + + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.2.3", "", { "os": "linux", "cpu": "x64" }, "sha512-LEtyYL1fJsvw35CxrbQ0gZoxOG3oZsAjzfRdvRBRHxOpQ91Q5doRVjvWW/wepgSdgk5hlaNzfeqpyGmfSD0Eyw=="], + + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.2.3", "", { "os": "linux", "cpu": "x64" }, "sha512-y76Dn4vkP1sMRGPFlNc+OTETBhGPJ90jY3il6jAfur8XWrYBQV3swZ1Jo0R2g+JpOeeoA0cOwM7mJG6svDz79w=="], + + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.2.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-Ms9zFYzjcJK7LV+AOMYnjN3pV3xL8Prxf9aWdDVL74onLn5kcvZ1ZMQswE5XHtnd/r/0bnUd928Rpbs14BzVmA=="], + + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.2.3", "", { "os": "win32", "cpu": "x64" }, "sha512-gvCpewE7mBwBIpqk1YrUqNR4mCiyJm6UI3YWQQXkedSSEwzRdodRpaKhbdbHw1/hmTWOVXQ+Eih5Qctf4TCVOQ=="], + + "@bufbuild/protobuf": ["@bufbuild/protobuf@2.7.0", "", {}, "sha512-qn6tAIZEw5i/wiESBF4nQxZkl86aY4KoO0IkUa2Lh+rya64oTOdJQFlZuMwI1Qz9VBJQrQC4QlSA2DNek5gCOA=="], + + "@connectrpc/connect": ["@connectrpc/connect@2.0.0-rc.3", "", { "peerDependencies": { "@bufbuild/protobuf": "^2.2.0" } }, "sha512-ARBt64yEyKbanyRETTjcjJuHr2YXorzQo0etyS5+P6oSeW8xEuzajA9g+zDnMcj1hlX2dQE93foIWQGfpru7gQ=="], + + "@connectrpc/connect-web": ["@connectrpc/connect-web@2.0.0-rc.3", "", { "peerDependencies": { "@bufbuild/protobuf": "^2.2.0", "@connectrpc/connect": "2.0.0-rc.3" } }, "sha512-w88P8Lsn5CCsA7MFRl2e6oLY4J/5toiNtJns/YJrlyQaWOy3RO8pDgkz+iIkG98RPMhj2thuBvsd3Cn4DKKCkw=="], + + "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], + + "@e2b/code-interpreter": ["@e2b/code-interpreter@2.0.0", "", { "dependencies": { "e2b": "^2.0.1" } }, "sha512-rCIW4dV544sUx2YQB/hhwDrK6sQzIb5lr5h/1CIVoOIRgU9q3NUxsFj+2OsgWd4rMG8l6b/oA7FVZJwP4sGX/A=="], + + "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="], + + "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.19.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.19.12", "", { "os": "android", "cpu": "arm" }, "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.19.12", "", { "os": "android", "cpu": "arm64" }, "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.19.12", "", { "os": "android", "cpu": "x64" }, "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.19.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.19.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.19.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.19.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.19.12", "", { "os": "linux", "cpu": "arm" }, "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.19.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.19.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.19.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.19.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.19.12", "", { "os": "linux", "cpu": "x64" }, "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.19.12", "", { "os": "none", "cpu": "x64" }, "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.19.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.19.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.19.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.19.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.19.12", "", { "os": "win32", "cpu": "x64" }, "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA=="], + + "@hono/zod-openapi": ["@hono/zod-openapi@1.1.0", "", { "dependencies": { "@asteasolutions/zod-to-openapi": "^8.1.0", "@hono/zod-validator": "^0.7.2", "openapi3-ts": "^4.5.0" }, "peerDependencies": { "hono": ">=4.3.6", "zod": "^4.0.0" } }, "sha512-S4jVR+A/jI4MA/RKJqmpjdHAN2l/EsqLnKHBv68x3WxV1NGVe3Sh7f6LV6rHEGYNHfiqpD75664A/erc+r9dQA=="], + + "@hono/zod-validator": ["@hono/zod-validator@0.2.2", "", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.19.1" } }, "sha512-dSDxaPV70Py8wuIU2QNpoVEIOSzSXZ/6/B/h4xA7eOMz7+AarKTSGV8E6QwrdcCbBLkpqfJ4Q2TmBO0eP1tCBQ=="], + + "@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="], + + "@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="], + + "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + + "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], + + "@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + + "@paralleldrive/cuid2": ["@paralleldrive/cuid2@2.2.2", "", { "dependencies": { "@noble/hashes": "^1.1.5" } }, "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA=="], + + "@shikijs/core": ["@shikijs/core@1.29.2", "", { "dependencies": { "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.4" } }, "sha512-vju0lY9r27jJfOY4Z7+Rt/nIOjzJpZ3y+nYpqtUZInVoXQ/TJZcfGnNOGnKjFdVZb8qexiCuSlZRKcGfhhTTZQ=="], + + "@shikijs/engine-javascript": ["@shikijs/engine-javascript@1.29.2", "", { "dependencies": { "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "oniguruma-to-es": "^2.2.0" } }, "sha512-iNEZv4IrLYPv64Q6k7EPpOCE/nuvGiKl7zxdq0WFuRPF5PAE9PRo2JGq/d8crLusM59BRemJ4eOqrFrC4wiQ+A=="], + + "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@1.29.2", "", { "dependencies": { "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1" } }, "sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA=="], + + "@shikijs/langs": ["@shikijs/langs@1.29.2", "", { "dependencies": { "@shikijs/types": "1.29.2" } }, "sha512-FIBA7N3LZ+223U7cJDUYd5shmciFQlYkFXlkKVaHsCPgfVLiO+e12FmQE6Tf9vuyEsFe3dIl8qGWKXgEHL9wmQ=="], + + "@shikijs/themes": ["@shikijs/themes@1.29.2", "", { "dependencies": { "@shikijs/types": "1.29.2" } }, "sha512-i9TNZlsq4uoyqSbluIcZkmPL9Bfi3djVxRnofUHwvx/h6SRW3cwgBC5SML7vsDcWyukY0eCzVN980rqP6qNl9g=="], + + "@shikijs/types": ["@shikijs/types@1.29.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw=="], + + "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], + + "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="], + + "@types/jsonwebtoken": ["@types/jsonwebtoken@9.0.10", "", { "dependencies": { "@types/ms": "*", "@types/node": "*" } }, "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA=="], + + "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], + + "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], + + "@types/node": ["@types/node@22.18.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-rzSDyhn4cYznVG+PCzGe1lwuMYJrcBS1fc3JqSa2PvtABwWo+dZ1ij5OVok3tqfpEBCBoaR4d7upFJk73HRJDw=="], + + "@types/react": ["@types/react@19.1.12", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w=="], + + "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], + + "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], + + "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "asap": ["asap@2.0.6", "", {}, "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="], + + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "aws-ssl-profiles": ["aws-ssl-profiles@1.1.2", "", {}, "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "better-sqlite3": ["better-sqlite3@9.6.0", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-yR5HATnqeYNVnkaUTf4bOP2dJSnyhP4puJN/QPRyx4YkBEEUxib422n2XzPqDEHjQQqazoYoADdAm5vE15+dAQ=="], + + "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="], + + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + + "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + + "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], + + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + + "bun-types": ["bun-types@1.2.21", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-sa2Tj77Ijc/NTLS0/Odjq/qngmEPZfbfnOERi0KRUYhT9R8M4VBioWVmMWE5GrYbKMc+5lVybXygLdibHaqVqw=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + + "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], + + "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], + + "character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], + + "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + + "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], + + "compare-versions": ["compare-versions@6.1.1", "", {}, "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg=="], + + "component-emitter": ["component-emitter@1.3.1", "", {}, "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ=="], + + "cookiejar": ["cookiejar@2.1.4", "", {}, "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], + + "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], + + "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], + + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + + "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="], + + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + + "detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="], + + "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + + "dezalgo": ["dezalgo@1.0.4", "", { "dependencies": { "asap": "^2.0.0", "wrappy": "1" } }, "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig=="], + + "dockerfile-ast": ["dockerfile-ast@0.7.1", "", { "dependencies": { "vscode-languageserver-textdocument": "^1.0.8", "vscode-languageserver-types": "^3.17.3" } }, "sha512-oX/A4I0EhSkGqrFv0YuvPkBUSYp1XiY8O8zAKc8Djglx8ocz+JfOr8gP0ryRMC2myqvDLagmnZaU9ot1vG2ijw=="], + + "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], + + "drizzle-kit": ["drizzle-kit@0.27.2", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.19.7", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-F6cFZ1wxa9XzFyeeQsp/0/lIzUbDuQjS8/njpYBDWa+wdWmXuY+Z/X2hHFK/9PGHZkv3c9mER+mVWfKlp/B6Vw=="], + + "drizzle-orm": ["drizzle-orm@0.33.0", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=3", "@electric-sql/pglite": ">=0.1.1", "@libsql/client": "*", "@neondatabase/serverless": ">=0.1", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/react": ">=18", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=13.2.0", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "react": ">=18", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/react", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "knex", "kysely", "mysql2", "pg", "postgres", "react", "sql.js", "sqlite3"] }, "sha512-SHy72R2Rdkz0LEq0PSG/IdvnT3nGiWuRk+2tXZQ90GVq/XQhpCzu/EFT3V2rox+w8MlkBQxifF8pCStNYnERfA=="], + + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "e2b": ["e2b@2.1.2", "", { "dependencies": { "@bufbuild/protobuf": "^2.6.2", "@connectrpc/connect": "2.0.0-rc.3", "@connectrpc/connect-web": "2.0.0-rc.3", "compare-versions": "^6.1.0", "dockerfile-ast": "^0.7.1", "glob": "^11.0.3", "openapi-fetch": "^0.9.7", "platform": "^1.3.6", "tar": "^7.4.3" } }, "sha512-3Z+cYCir7VllmZ72N7O5lhGqEi1A6s4rD8pQnBH5S9g12+9HkhJf7RsbcHJhWNsvEbFQ0p0GHBQ4RgugWKYybw=="], + + "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + + "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], + + "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + + "emoji-regex-xs": ["emoji-regex-xs@1.0.0", "", {}, "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg=="], + + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + + "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + + "esbuild": ["esbuild@0.19.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.19.12", "@esbuild/android-arm": "0.19.12", "@esbuild/android-arm64": "0.19.12", "@esbuild/android-x64": "0.19.12", "@esbuild/darwin-arm64": "0.19.12", "@esbuild/darwin-x64": "0.19.12", "@esbuild/freebsd-arm64": "0.19.12", "@esbuild/freebsd-x64": "0.19.12", "@esbuild/linux-arm": "0.19.12", "@esbuild/linux-arm64": "0.19.12", "@esbuild/linux-ia32": "0.19.12", "@esbuild/linux-loong64": "0.19.12", "@esbuild/linux-mips64el": "0.19.12", "@esbuild/linux-ppc64": "0.19.12", "@esbuild/linux-riscv64": "0.19.12", "@esbuild/linux-s390x": "0.19.12", "@esbuild/linux-x64": "0.19.12", "@esbuild/netbsd-x64": "0.19.12", "@esbuild/openbsd-x64": "0.19.12", "@esbuild/sunos-x64": "0.19.12", "@esbuild/win32-arm64": "0.19.12", "@esbuild/win32-ia32": "0.19.12", "@esbuild/win32-x64": "0.19.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg=="], + + "esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="], + + "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], + + "fast-safe-stringify": ["fast-safe-stringify@2.1.1", "", {}, "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="], + + "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="], + + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + + "form-data": ["form-data@4.0.4", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="], + + "formidable": ["formidable@3.5.4", "", { "dependencies": { "@paralleldrive/cuid2": "^2.2.2", "dezalgo": "^1.0.4", "once": "^1.4.0" } }, "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug=="], + + "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "generate-function": ["generate-function@2.3.1", "", { "dependencies": { "is-property": "^1.0.2" } }, "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "get-tsconfig": ["get-tsconfig@4.10.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ=="], + + "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], + + "glob": ["glob@11.0.3", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.0.3", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + + "hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="], + + "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], + + "hono": ["hono@4.9.6", "", {}, "sha512-doVjXhSFvYZ7y0dNokjwwSahcrAfdz+/BCLvAMa/vHLzjj8+CFyV5xteThGUsKdkaasgN+gF2mUxao+SGLpUeA=="], + + "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], + + "iconv-lite": ["iconv-lite@0.7.0", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-property": ["is-property@1.0.2", "", {}, "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "jackspeak": ["jackspeak@4.1.1", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" } }, "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ=="], + + "jsonwebtoken": ["jsonwebtoken@9.0.2", "", { "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ=="], + + "jwa": ["jwa@1.4.2", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw=="], + + "jws": ["jws@3.2.2", "", { "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } }, "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA=="], + + "linkify-it": ["linkify-it@5.0.0", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ=="], + + "lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="], + + "lodash.isboolean": ["lodash.isboolean@3.0.3", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="], + + "lodash.isinteger": ["lodash.isinteger@4.0.4", "", {}, "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="], + + "lodash.isnumber": ["lodash.isnumber@3.0.3", "", {}, "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="], + + "lodash.isplainobject": ["lodash.isplainobject@4.0.6", "", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="], + + "lodash.isstring": ["lodash.isstring@4.0.1", "", {}, "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="], + + "lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="], + + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + + "lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], + + "lru.min": ["lru.min@1.1.2", "", {}, "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg=="], + + "lunr": ["lunr@2.3.9", "", {}, "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow=="], + + "markdown-it": ["markdown-it@14.1.0", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "mdast-util-to-hast": ["mdast-util-to-hast@13.2.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA=="], + + "mdurl": ["mdurl@2.0.0", "", {}, "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="], + + "methods": ["methods@1.1.2", "", {}, "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="], + + "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], + + "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="], + + "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], + + "mime": ["mime@2.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg=="], + + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], + + "minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + + "minizlib": ["minizlib@3.0.2", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA=="], + + "mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="], + + "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "mysql2": ["mysql2@3.14.5", "", { "dependencies": { "aws-ssl-profiles": "^1.1.1", "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.7.0", "long": "^5.2.1", "lru.min": "^1.0.0", "named-placeholders": "^1.1.3", "seq-queue": "^0.0.5", "sqlstring": "^2.3.2" } }, "sha512-40hDf8LPUsuuJ2hFq+UgOuPwt2IFLIRDvMv6ez9hKbXeYuZPxDDwiJW7KdknvOsQqKznaKczOT1kELgFkhDvFg=="], + + "named-placeholders": ["named-placeholders@1.1.3", "", { "dependencies": { "lru-cache": "^7.14.1" } }, "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w=="], + + "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], + + "node-abi": ["node-abi@3.77.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-DSmt0OEcLoK4i3NuscSbGjOf3bqiDEutejqENSplMSFA/gmB8mkED9G4pKWnPl7MDU4rSHebKPHeitpDfyH0cQ=="], + + "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "oniguruma-to-es": ["oniguruma-to-es@2.3.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.1.1", "regex-recursion": "^5.1.1" } }, "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g=="], + + "openapi-fetch": ["openapi-fetch@0.9.8", "", { "dependencies": { "openapi-typescript-helpers": "^0.0.8" } }, "sha512-zM6elH0EZStD/gSiNlcPrzXcVQ/pZo3BDvC6CDwRDUt1dDzxlshpmQnpD6cZaJ39THaSmwVCxxRrPKNM1hHrDg=="], + + "openapi-typescript-helpers": ["openapi-typescript-helpers@0.0.8", "", {}, "sha512-1eNjQtbfNi5Z/kFhagDIaIRj6qqDzhjNJKz8cmMW0CVdGwT6e1GLbAfgI0d28VTJa1A8jz82jm/4dG8qNoNS8g=="], + + "openapi3-ts": ["openapi3-ts@4.5.0", "", { "dependencies": { "yaml": "^2.8.0" } }, "sha512-jaL+HgTq2Gj5jRcfdutgRGLosCy/hT8sQf6VOy+P+g36cZOjI1iukdPnijC+4CmeRzg/jEllJUboEic2FhxhtQ=="], + + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-scurry": ["path-scurry@2.0.0", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg=="], + + "pg": ["pg@8.16.3", "", { "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", "pg-protocol": "^1.10.3", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.2.7" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw=="], + + "pg-cloudflare": ["pg-cloudflare@1.2.7", "", {}, "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg=="], + + "pg-connection-string": ["pg-connection-string@2.9.1", "", {}, "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w=="], + + "pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="], + + "pg-pool": ["pg-pool@3.10.1", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg=="], + + "pg-protocol": ["pg-protocol@1.10.3", "", {}, "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ=="], + + "pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="], + + "pgpass": ["pgpass@1.0.5", "", { "dependencies": { "split2": "^4.1.0" } }, "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug=="], + + "platform": ["platform@1.3.6", "", {}, "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg=="], + + "postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="], + + "postgres-bytea": ["postgres-bytea@1.0.0", "", {}, "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w=="], + + "postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="], + + "postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="], + + "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], + + "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], + + "pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="], + + "punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="], + + "qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="], + + "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], + + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + + "regex": ["regex@5.1.1", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw=="], + + "regex-recursion": ["regex-recursion@5.1.1", "", { "dependencies": { "regex": "^5.1.1", "regex-utilities": "^2.3.0" } }, "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w=="], + + "regex-utilities": ["regex-utilities@2.3.0", "", {}, "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="], + + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], + + "seq-queue": ["seq-queue@0.0.5", "", {}, "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "shiki": ["shiki@1.29.2", "", { "dependencies": { "@shikijs/core": "1.29.2", "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/langs": "1.29.2", "@shikijs/themes": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg=="], + + "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], + + "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], + + "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], + + "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], + + "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], + + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + + "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], + + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + + "sqlstring": ["sqlstring@2.3.3", "", {}, "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg=="], + + "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + + "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], + + "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + + "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], + + "superagent": ["superagent@10.2.3", "", { "dependencies": { "component-emitter": "^1.3.1", "cookiejar": "^2.1.4", "debug": "^4.3.7", "fast-safe-stringify": "^2.1.1", "form-data": "^4.0.4", "formidable": "^3.5.4", "methods": "^1.1.2", "mime": "2.6.0", "qs": "^6.11.2" } }, "sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig=="], + + "supertest": ["supertest@7.1.4", "", { "dependencies": { "methods": "^1.1.2", "superagent": "^10.2.3" } }, "sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg=="], + + "tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="], + + "tar-fs": ["tar-fs@2.1.3", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg=="], + + "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], + + "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], + + "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], + + "typedoc": ["typedoc@0.26.11", "", { "dependencies": { "lunr": "^2.3.9", "markdown-it": "^14.1.0", "minimatch": "^9.0.5", "shiki": "^1.16.2", "yaml": "^2.5.1" }, "peerDependencies": { "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x" }, "bin": { "typedoc": "bin/typedoc" } }, "sha512-sFEgRRtrcDl2FxVP58Ze++ZK2UQAEvtvvH8rRlig1Ja3o7dDaMHmaBfvJmdGnNEFaLTpQsN8dpvZaTqJSu/Ugw=="], + + "typescript": ["typescript@5.6.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw=="], + + "uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="], + + "undici": ["undici@6.21.3", "", {}, "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw=="], + + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "unist-util-is": ["unist-util-is@6.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw=="], + + "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="], + + "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], + + "unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="], + + "unist-util-visit-parents": ["unist-util-visit-parents@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "uuid": ["uuid@12.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-USe1zesMYh4fjCA8ZH5+X5WIVD0J4V1Jksm1bFTVBX2F/cwSXt0RO5w/3UXbdLKmZX65MiWV+hwhSS8p6oBTGA=="], + + "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], + + "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], + + "vscode-languageserver-textdocument": ["vscode-languageserver-textdocument@1.0.12", "", {}, "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA=="], + + "vscode-languageserver-types": ["vscode-languageserver-types@3.17.5", "", {}, "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + + "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], + + "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + + "yaml": ["yaml@2.8.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw=="], + + "zod": ["zod@4.1.5", "", {}, "sha512-rcUUZqlLJgBC33IT3PNMgsCq6TzLQEG/Ei/KTCU0PedSWRMAXoOUN+4t/0H+Q8bdnLPdqUYnvboJT0bn/229qg=="], + + "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], + + "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], + + "@hono/zod-openapi/@hono/zod-validator": ["@hono/zod-validator@0.7.2", "", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-ub5eL/NeZ4eLZawu78JpW/J+dugDAYhwqUIdp9KYScI6PZECij4Hx4UsrthlEUutqDDhPwRI0MscUfNkvn/mqQ=="], + + "glob/minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="], + + "path-scurry/lru-cache": ["lru-cache@11.2.1", "", {}, "sha512-r8LA6i4LP4EeWOhqBaZZjDWwehd1xUJPCJd9Sv300H0ZmcUER4+JPh7bqqZeqs1o5pgtgvXm+d9UGrB5zZGDiQ=="], + + "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "tar-fs/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], + + "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], + + "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + } +} diff --git a/apps/coderouter/package.json b/apps/coderouter/package.json index 7762e2655d..8a3296a463 100644 --- a/apps/coderouter/package.json +++ b/apps/coderouter/package.json @@ -13,7 +13,7 @@ "db:migrate": "bun run scripts/migrate.ts" }, "dependencies": { - "@e2b/code-interpreter": "1.5.1", + "@e2b/code-interpreter": "2.0.0", "@hono/zod-openapi": "^1.1.0", "@hono/zod-validator": "^0.2.1", "@types/jsonwebtoken": "^9.0.10", diff --git a/apps/coderouter/src/api/sandbox/file/watch/index.ts b/apps/coderouter/src/api/sandbox/file/watch/index.ts index 1c9f70c1b3..8c39ef5af5 100644 --- a/apps/coderouter/src/api/sandbox/file/watch/index.ts +++ b/apps/coderouter/src/api/sandbox/file/watch/index.ts @@ -3,6 +3,8 @@ import { LocalHono } from '@/server'; import { JwtAuthResponses } from '@/util/auth'; import { createRoute, z } from '@hono/zod-openapi'; import { env } from 'bun'; +import { streamSSE } from 'hono/streaming'; +import { v4 as uuid } from 'uuid'; const BodySchema: z.ZodType = z.object({ path: z.string().openapi({ @@ -19,15 +21,6 @@ const BodySchema: z.ZodType = z.object({ }), }); -const ResponseSchema: z.ZodType = z.object({ - events: z.array( - z.object({ - path: z.string(), - type: z.enum(['create', 'update', 'delete']), - }), - ), -}); - const route = createRoute({ method: 'post', path: env.URL_PATH_PREFIX + '/api/sandbox/file/watch', @@ -44,11 +37,13 @@ const route = createRoute({ responses: { 200: { content: { - 'application/json': { - schema: ResponseSchema, + 'text/event-stream': { + schema: z.string().openapi({ + description: 'A stream of server-sent events.', + }), }, }, - description: 'Watch a file to the sandbox.', + description: 'Return file watch events. Design for SSE', }, ...JwtAuthResponses, }, @@ -56,8 +51,39 @@ const route = createRoute({ export function api_sandbox_file_watch(app: LocalHono) { app.openapi(route, async (c) => { - const body = await c.req.valid('json'); - const result = await c.get('client').sandbox.file.watch(body); - return c.json(result, 200); + const query = await c.req.valid('json'); + return streamSSE(c, async (stream) => { + // Send a "connected" message immediately + await stream.writeSSE({ + id: uuid(), + event: 'status', + data: 'Connected to endpoint.', + }); + + const onOutput = (res: SandboxFileWatchOutput) => { + stream.writeSSE({ + id: res.id, + event: 'message', + data: JSON.stringify({ events: res.events }), + }); + }; + + const { close } = await c.get('client').sandbox.file.watch(query, onOutput); + + let open = true; + stream.onAbort(() => { + close(); + open = false; + }); + + while (open) { + await stream.writeSSE({ + id: uuid(), + event: 'status', + data: 'Endpoint is still open.', + }); + await stream.sleep(5000); + } + }); }); } diff --git a/apps/coderouter/src/api/sandbox/terminal/command/index.ts b/apps/coderouter/src/api/sandbox/terminal/command/index.ts index afe2cab545..df43939c90 100644 --- a/apps/coderouter/src/api/sandbox/terminal/command/index.ts +++ b/apps/coderouter/src/api/sandbox/terminal/command/index.ts @@ -16,22 +16,22 @@ const BodySchema: z.ZodType = z.object({ const ResponseSchema: z.ZodType = z.object({ is_error: z.boolean().openapi({ - description: 'Whether the command was successful.', - example: true, + description: 'True if the command failed.', + example: false, }), output: z.string().openapi({ - description: 'The output of the command.', - example: 'ls -la', + description: 'Combined output (stdout + stderr).', + example: 'total 0', }), - stdout: z.string().openapi({ + stdout: z.string().optional().openapi({ description: 'The stdout of the command.', - example: 'ls -la', + example: 'total 0', }), - stderr: z.string().openapi({ + stderr: z.string().optional().openapi({ description: 'The stderr of the command.', - example: 'ls -la', + example: '', }), - exit_code: z.number().openapi({ + exit_code: z.number().optional().openapi({ description: 'The exit code of the command.', example: 0, }), diff --git a/apps/coderouter/src/api/sandbox/terminal/open/index.ts b/apps/coderouter/src/api/sandbox/terminal/open/index.ts index 8d6ba21cc3..68a8efaff4 100644 --- a/apps/coderouter/src/api/sandbox/terminal/open/index.ts +++ b/apps/coderouter/src/api/sandbox/terminal/open/index.ts @@ -6,7 +6,7 @@ import { SandboxTerminalOpenOutput } from '@/provider/definition/sandbox/termina import { LocalHono } from '@/server'; import { JwtAuthResponses } from '@/util/auth'; import { createRoute, z } from '@hono/zod-openapi'; -import { env, sleep } from 'bun'; +import { env } from 'bun'; import { streamSSE } from 'hono/streaming'; import { v4 as uuid } from 'uuid'; @@ -29,12 +29,11 @@ const route = createRoute({ content: { 'text/event-stream': { schema: z.string().openapi({ - description: - 'A stream of server-sent events (not JSON array, continuous text).', + description: 'A stream of server-sent events.', }), }, }, - description: 'Return the output of a terminal in the sandbox. Design for long-polling', + description: 'Return the output of a terminal in the sandbox. Design for SSE', }, ...JwtAuthResponses, }, @@ -52,7 +51,6 @@ export function api_sandbox_terminal_open(app: LocalHono) { }); const onOutput = (res: SandboxTerminalOpenOutput) => { - console.log(res.output); stream.writeSSE({ id: res.id, event: 'message', @@ -74,7 +72,7 @@ export function api_sandbox_terminal_open(app: LocalHono) { event: 'status', data: 'Endpoint is still open.', }); - await sleep(5000); + await stream.sleep(5000); } }); }); diff --git a/apps/coderouter/src/api/sandbox/terminal/run/index.ts b/apps/coderouter/src/api/sandbox/terminal/run/index.ts index 390c12df60..9393894225 100644 --- a/apps/coderouter/src/api/sandbox/terminal/run/index.ts +++ b/apps/coderouter/src/api/sandbox/terminal/run/index.ts @@ -21,8 +21,8 @@ const BodySchema: z.ZodType = z.object({ const ResponseSchema: z.ZodType = z.object({ output: z.string().openapi({ description: - 'The output of the run command. The output includes lines before and after the command execution.', - example: 'My Terminal', + 'Combined stdout/stderr captured from the terminal around the command execution.', + example: '$ ls -la\\n.\\n..\\nREADME.md\\n', }), }); diff --git a/apps/coderouter/src/middleware/beforeSandboxCall.ts b/apps/coderouter/src/middleware/beforeSandboxCall.ts index c824012b6b..07fade6155 100644 --- a/apps/coderouter/src/middleware/beforeSandboxCall.ts +++ b/apps/coderouter/src/middleware/beforeSandboxCall.ts @@ -1,15 +1,13 @@ -import { LocalHono } from "@/server"; +import { LocalHono } from '@/server'; export function setupBeforeSandboxCallMiddleware(app: LocalHono, path: string) { - return app.use(path, async (c, next) => { - const client = c.get("client"); - if (!client) { - console.error( - "The provider client is not set. Please check the middleware setup." - ); - return c.json({ error: "Client not found" }, 500); - } - await client.sandbox.beforeSandboxCall(); - await next(); - }); + return app.use(path, async (c, next) => { + const client = c.get('client'); + if (!client) { + console.error('The provider client is not set. Please check the middleware setup.'); + return c.json({ error: 'Client not found' }, 500); + } + await client.sandbox.beforeSandboxCall(); + await next(); + }); } diff --git a/apps/coderouter/src/provider/definition/sandbox/file/index.ts b/apps/coderouter/src/provider/definition/sandbox/file/index.ts index 0297587b73..656fb0289a 100644 --- a/apps/coderouter/src/provider/definition/sandbox/file/index.ts +++ b/apps/coderouter/src/provider/definition/sandbox/file/index.ts @@ -57,6 +57,7 @@ export interface SandboxFileWatchInput { excludePaths: string[]; } export interface SandboxFileWatchOutput { + id: string; events: Array<{ path: string; type: 'create' | 'update' | 'delete'; @@ -73,6 +74,9 @@ export abstract class SandboxFile { abstract read(input: SandboxFileReadInput): Promise; abstract rename(input: SandboxFileRenameInput): Promise; abstract stat(input: SandboxFileStatInput): Promise; - abstract watch(input: SandboxFileWatchInput): Promise; + abstract watch( + input: SandboxFileWatchInput, + onOutput: (output: SandboxFileWatchOutput) => void, + ): Promise<{ close: () => void }>; abstract write(input: SandboxFileWriteInput): Promise; } diff --git a/apps/coderouter/src/provider/definition/sandbox/terminal/index.ts b/apps/coderouter/src/provider/definition/sandbox/terminal/index.ts index 73843a50d2..67820a54b4 100644 --- a/apps/coderouter/src/provider/definition/sandbox/terminal/index.ts +++ b/apps/coderouter/src/provider/definition/sandbox/terminal/index.ts @@ -32,9 +32,7 @@ export interface SandboxTerminalWriteInput { terminalId: string; input: string; } -export interface SandboxTerminalWriteOutput { - output: string; -} +export interface SandboxTerminalWriteOutput {} export interface SandboxTerminalKillInput { terminalId: string; @@ -47,9 +45,7 @@ export interface SandboxTerminalRunInput { terminalId: string; input: string; } -export interface SandboxTerminalRunOutput { - output: string; -} +export interface SandboxTerminalRunOutput {} export abstract class SandboxTerminal { constructor(protected readonly client: T) {} diff --git a/apps/coderouter/src/provider/e2b/sandbox/file/index.ts b/apps/coderouter/src/provider/e2b/sandbox/file/index.ts index 8eb0c343a9..14178bfa45 100644 --- a/apps/coderouter/src/provider/e2b/sandbox/file/index.ts +++ b/apps/coderouter/src/provider/e2b/sandbox/file/index.ts @@ -23,6 +23,7 @@ import { } from '../../../definition/sandbox/file'; import { NotFoundError, WatchHandle } from '@e2b/code-interpreter'; import path from 'path'; +import { v4 as uuid } from 'uuid'; const watchers: Map = new Map(); @@ -167,7 +168,7 @@ export class E2BSandboxFile extends SandboxFile { return {}; } - async watch(input: SandboxFileWatchInput): Promise { + async watch(input: SandboxFileWatchInput, onOutput: (output: SandboxFileWatchOutput) => void) { if (!this.client._sandbox) { throw new ClientError( ClientErrorCode.ImplementationError, @@ -205,13 +206,12 @@ export class E2BSandboxFile extends SandboxFile { }); } - const events = await watcher.list(); + watcher.onOutput((output) => onOutput(output)); return { - events: events.map((event) => ({ - path: event.path, - type: event.type, - })), + close: () => { + watcher.stop(); + }, }; } @@ -235,8 +235,7 @@ class Watcher { protected _off: boolean = true; protected _watchHandle: WatchHandle | null = null; - protected _timeout: NodeJS.Timeout | null = null; - protected _resolvers: Array<() => void> = []; + protected _onEventCallbacks: Array<(output: SandboxFileWatchOutput) => void> = []; constructor(protected readonly client: E2BClient) {} @@ -289,12 +288,15 @@ class Watcher { // debounce the resolution of the promise timeout = setTimeout(() => { - this._resolvers.forEach((resolve) => resolve()); - this._resolvers = []; - }, 300); + this._onEventCallbacks.forEach((callback) => + callback({ + id: uuid(), + events: this.events, + }), + ); + }, 200); }, { - // requestTimeoutMs: 0, timeoutMs: 0, recursive: input.recursive, onExit: (err) => { @@ -305,39 +307,8 @@ class Watcher { ); } - async list(): Promise> { - if (this._timeout) { - clearTimeout(this._timeout); - this._timeout = null; - } - - // if there hasn't been any calls to `list` within the timeout, stop the watcher - this._timeout = setTimeout(() => { - this.stop(); - }, this.watchTimeout); - - // if there are no events, do not return yet - if (this.events.length === 0) { - let resolved = false; - await new Promise((resolve) => { - this._resolvers.push(() => { - if (!resolved) { - resolve(); - resolved = true; - } - }); - setTimeout(() => { - if (!resolved) { - resolve(); - resolved = true; - } - }, this.promiseTimeout); - }); - } - - const events = this.events; - this.events = []; - return events; + onOutput(callback: (output: SandboxFileWatchOutput) => void): void { + this._onEventCallbacks.push(callback); } async stop(): Promise { diff --git a/apps/coderouter/src/provider/e2b/sandbox/index.ts b/apps/coderouter/src/provider/e2b/sandbox/index.ts index 37ffc590c9..e62a412c80 100644 --- a/apps/coderouter/src/provider/e2b/sandbox/index.ts +++ b/apps/coderouter/src/provider/e2b/sandbox/index.ts @@ -14,7 +14,7 @@ import { SandboxUrlInput, SandboxUrlOutput, } from '@/provider/definition/sandbox'; -import { Sandbox as _E2BSandbox } from '@e2b/code-interpreter'; +import { Sandbox as _E2BSandbox, SandboxState } from '@e2b/code-interpreter'; import { ClientError, ClientErrorCode } from '@/provider/definition'; import { E2BSandboxFile } from './file'; import { E2BSandboxTerminal } from './terminal'; @@ -23,6 +23,8 @@ export class E2BSandbox extends Sandbox { public readonly file: E2BSandboxFile; public readonly terminal: E2BSandboxTerminal; + private _sandboxTimeoutMs: number = 1000 * 60 * 3; + constructor(protected readonly client: E2BClient) { super(client); this.file = new E2BSandboxFile(this.client); @@ -33,8 +35,7 @@ export class E2BSandbox extends Sandbox { const e2bSandboxId = (await this.get({})).externalId; this.client._sandbox = await _E2BSandbox.connect(e2bSandboxId); // bump the timeout to 5 minutes - // this.client._sandbox.setTimeout(1000 * 60 * 5); - this.client._sandbox.setTimeout(1000 * 60 * 30); + this.client._sandbox.setTimeout(this._sandboxTimeoutMs); } async create(input: SandboxCreateInput): Promise { @@ -59,10 +60,11 @@ export class E2BSandbox extends Sandbox { if (this.client.options.userId) { metadata['userId'] = this.client.options.userId; } - this.client._sandbox = await _E2BSandbox.create(input.templateId, { + this.client._sandbox = await _E2BSandbox.betaCreate(input.templateId, { apiKey: this.client.apiKey, - timeoutMs: 1000 * 60 * 30, + timeoutMs: this._sandboxTimeoutMs, metadata, + autoPause: true, }); return { externalId: this.client._sandbox.sandboxId, @@ -77,13 +79,15 @@ export class E2BSandbox extends Sandbox { false, ); } - const query: { metadata: Record } = { + const query: { state: SandboxState[]; metadata: Record } = { + state: ['running', 'paused'], metadata: { sandboxId: this.client.options.sandboxId }, }; if (this.client.options.userId) { query.metadata['userId'] = this.client.options.userId; } - const list = await _E2BSandbox.list({ query }); + const res = await _E2BSandbox.list({ query }); + const list = await res.nextItems(); const e2bSandbox = list?.[0]; if (!e2bSandbox) { throw new ClientError( @@ -106,6 +110,14 @@ export class E2BSandbox extends Sandbox { } async pause(input: SandboxPauseInput): Promise { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ); + } + await this.client._sandbox.betaPause(); return {}; } diff --git a/apps/coderouter/src/provider/e2b/sandbox/terminal/index.ts b/apps/coderouter/src/provider/e2b/sandbox/terminal/index.ts index 5efc0e1b07..ee2057c983 100644 --- a/apps/coderouter/src/provider/e2b/sandbox/terminal/index.ts +++ b/apps/coderouter/src/provider/e2b/sandbox/terminal/index.ts @@ -115,11 +115,11 @@ export class E2BSandboxTerminal extends SandboxTerminal { await watcher.start(); } - watcher.onOutput((output) => onOutput(output)); + const unsubscribe = watcher.onOutput((output) => onOutput(output)); return { close: () => { - watcher.stop(); + unsubscribe(); }, }; } @@ -145,9 +145,7 @@ export class E2BSandboxTerminal extends SandboxTerminal { // Write input to the terminal await watcher.writeInput(input.input); - return { - output: '', - }; + return {}; } async run(input: SandboxTerminalRunInput): Promise { @@ -171,9 +169,7 @@ export class E2BSandboxTerminal extends SandboxTerminal { // Execute the command and get output await watcher.executeCommand(input.input); - return { - output: '', - }; + return {}; } async kill(input: SandboxTerminalKillInput): Promise { @@ -254,13 +250,17 @@ class TerminalWatcher { console.error('error waiting for terminal handle', err); throw err; } finally { - console.log('stopping terminal watcher'); this.stop(); } } - onOutput(callback: (output: SandboxTerminalOpenOutput) => void): void { + onOutput(callback: (output: SandboxTerminalOpenOutput) => void): () => void { this._onOutputCallbacks.push(callback); + return () => { + this._onOutputCallbacks = this._onOutputCallbacks.filter( + (callback) => callback !== callback, + ); + }; } async writeInput(input: string): Promise { diff --git a/apps/web/client/src/components/store/editor/sandbox/index.ts b/apps/web/client/src/components/store/editor/sandbox/index.ts index 3a2e54ad07..3d742380cc 100644 --- a/apps/web/client/src/components/store/editor/sandbox/index.ts +++ b/apps/web/client/src/components/store/editor/sandbox/index.ts @@ -392,23 +392,23 @@ export class SandboxManager { // Convert ignored directories to glob patterns with ** wildcard const excludePatterns = EXCLUDED_SYNC_DIRECTORIES.map((dir) => `${dir}/**`); - // const res = await this.session.provider.watchFiles({ - // args: { - // path: './', - // recursive: true, - // excludes: excludePatterns, - // }, - // onFileChange: async (event) => { - // this.fileEventBus.publish({ - // type: event.type, - // paths: event.paths, - // timestamp: Date.now(), - // }); - // await this.handleFileChange(event); - // }, - // }); - - // this.fileWatcher = res.watcher; + const res = await this.session.provider.watchFiles({ + args: { + path: './', + recursive: true, + excludes: excludePatterns, + }, + onFileChange: async (event) => { + this.fileEventBus.publish({ + type: event.type, + paths: event.paths, + timestamp: Date.now(), + }); + await this.handleFileChange(event); + }, + }); + + this.fileWatcher = res.watcher; } async handleFileChange(event: WatchEvent) { diff --git a/bun.lock b/bun.lock index 262cb9d22e..3b56d7f91d 100644 --- a/bun.lock +++ b/bun.lock @@ -23,7 +23,7 @@ "name": "coderouter", "version": "0.0.1", "dependencies": { - "@e2b/code-interpreter": "1.5.1", + "@e2b/code-interpreter": "2.0.0", "@hono/zod-openapi": "^1.1.0", "@hono/zod-validator": "^0.2.1", "@types/jsonwebtoken": "^9.0.10", @@ -834,7 +834,7 @@ "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], - "@e2b/code-interpreter": ["@e2b/code-interpreter@1.5.1", "", { "dependencies": { "e2b": "^1.4.0" } }, "sha512-mkyKjAW2KN5Yt0R1I+1lbH3lo+W/g/1+C2lnwlitXk5wqi/g94SEO41XKdmDf5WWpKG3mnxWDR5d6S/lyjmMEw=="], + "@e2b/code-interpreter": ["@e2b/code-interpreter@2.0.0", "", { "dependencies": { "e2b": "^2.0.1" } }, "sha512-rCIW4dV544sUx2YQB/hhwDrK6sQzIb5lr5h/1CIVoOIRgU9q3NUxsFj+2OsgWd4rMG8l6b/oA7FVZJwP4sGX/A=="], "@emnapi/core": ["@emnapi/core@1.4.5", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.4", "tslib": "^2.4.0" } }, "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q=="], @@ -2510,6 +2510,8 @@ "dnd-core": ["dnd-core@14.0.1", "", { "dependencies": { "@react-dnd/asap": "^4.0.0", "@react-dnd/invariant": "^2.0.0", "redux": "^4.1.1" } }, "sha512-+PVS2VPTgKFPYWo3vAFEA8WPbTf7/xo43TifH9G8S1KqnrQu0o77A3unrF5yOugy4mIz7K5wAVFHUcha7wsz6A=="], + "dockerfile-ast": ["dockerfile-ast@0.7.1", "", { "dependencies": { "vscode-languageserver-textdocument": "^1.0.8", "vscode-languageserver-types": "^3.17.3" } }, "sha512-oX/A4I0EhSkGqrFv0YuvPkBUSYp1XiY8O8zAKc8Djglx8ocz+JfOr8gP0ryRMC2myqvDLagmnZaU9ot1vG2ijw=="], + "doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="], "dom-helpers": ["dom-helpers@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA=="], @@ -2538,7 +2540,7 @@ "duplexify": ["duplexify@4.1.3", "", { "dependencies": { "end-of-stream": "^1.4.1", "inherits": "^2.0.3", "readable-stream": "^3.1.1", "stream-shift": "^1.0.2" } }, "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA=="], - "e2b": ["e2b@1.13.2", "", { "dependencies": { "@bufbuild/protobuf": "^2.6.2", "@connectrpc/connect": "2.0.0-rc.3", "@connectrpc/connect-web": "2.0.0-rc.3", "compare-versions": "^6.1.0", "openapi-fetch": "^0.9.7", "platform": "^1.3.6" } }, "sha512-m8acE/MzMAJo1A57DakR2X1Sl5Mt1tcQO2aJfygNaQHLXby/4xsjF0UeJUB70jF7xntiR41pAMbZEHnkzrT9tw=="], + "e2b": ["e2b@2.1.2", "", { "dependencies": { "@bufbuild/protobuf": "^2.6.2", "@connectrpc/connect": "2.0.0-rc.3", "@connectrpc/connect-web": "2.0.0-rc.3", "compare-versions": "^6.1.0", "dockerfile-ast": "^0.7.1", "glob": "^11.0.3", "openapi-fetch": "^0.9.7", "platform": "^1.3.6", "tar": "^7.4.3" } }, "sha512-3Z+cYCir7VllmZ72N7O5lhGqEi1A6s4rD8pQnBH5S9g12+9HkhJf7RsbcHJhWNsvEbFQ0p0GHBQ4RgugWKYybw=="], "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], @@ -4424,6 +4426,10 @@ "vlq": ["vlq@1.0.1", "", {}, "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w=="], + "vscode-languageserver-textdocument": ["vscode-languageserver-textdocument@1.0.12", "", {}, "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA=="], + + "vscode-languageserver-types": ["vscode-languageserver-types@3.17.5", "", {}, "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg=="], + "w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="], "walker": ["walker@1.0.8", "", { "dependencies": { "makeerror": "1.0.12" } }, "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ=="], diff --git a/packages/code-provider/src/providers/coderouter/codegen/.openapi-generator/FILES b/packages/code-provider/src/providers/coderouter/codegen/.openapi-generator/FILES index e9de06b63c..7558ef43a2 100644 --- a/packages/code-provider/src/providers/coderouter/codegen/.openapi-generator/FILES +++ b/packages/code-provider/src/providers/coderouter/codegen/.openapi-generator/FILES @@ -17,8 +17,6 @@ models/CoderouterApiSandboxFileReadPostRequest.ts models/CoderouterApiSandboxFileRenamePostRequest.ts models/CoderouterApiSandboxFileStatPost200Response.ts models/CoderouterApiSandboxFileStatPostRequest.ts -models/CoderouterApiSandboxFileWatchPost200Response.ts -models/CoderouterApiSandboxFileWatchPost200ResponseEventsInner.ts models/CoderouterApiSandboxFileWatchPostRequest.ts models/CoderouterApiSandboxFileWritePostRequest.ts models/CoderouterApiSandboxFileWritePostRequestFilesInner.ts diff --git a/packages/code-provider/src/providers/coderouter/codegen/apis/DefaultApi.ts b/packages/code-provider/src/providers/coderouter/codegen/apis/DefaultApi.ts index 84e7d00421..93ae19670f 100644 --- a/packages/code-provider/src/providers/coderouter/codegen/apis/DefaultApi.ts +++ b/packages/code-provider/src/providers/coderouter/codegen/apis/DefaultApi.ts @@ -29,7 +29,6 @@ import type { CoderouterApiSandboxFileRenamePostRequest, CoderouterApiSandboxFileStatPost200Response, CoderouterApiSandboxFileStatPostRequest, - CoderouterApiSandboxFileWatchPost200Response, CoderouterApiSandboxFileWatchPostRequest, CoderouterApiSandboxFileWritePostRequest, CoderouterApiSandboxTerminalCommandPost200Response, @@ -74,8 +73,6 @@ import { CoderouterApiSandboxFileStatPost200ResponseToJSON, CoderouterApiSandboxFileStatPostRequestFromJSON, CoderouterApiSandboxFileStatPostRequestToJSON, - CoderouterApiSandboxFileWatchPost200ResponseFromJSON, - CoderouterApiSandboxFileWatchPost200ResponseToJSON, CoderouterApiSandboxFileWatchPostRequestFromJSON, CoderouterApiSandboxFileWatchPostRequestToJSON, CoderouterApiSandboxFileWritePostRequestFromJSON, @@ -666,7 +663,7 @@ export class DefaultApi extends runtime.BaseAPI { async coderouterApiSandboxFileWatchPostRaw( requestParameters: CoderouterApiSandboxFileWatchPostOperationRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction, - ): Promise> { + ): Promise> { const queryParameters: any = {}; const headerParameters: runtime.HTTPHeaders = {}; @@ -697,9 +694,11 @@ export class DefaultApi extends runtime.BaseAPI { initOverrides, ); - return new runtime.JSONApiResponse(response, (jsonValue) => - CoderouterApiSandboxFileWatchPost200ResponseFromJSON(jsonValue), - ); + if (this.isJsonMime(response.headers.get('content-type'))) { + return new runtime.JSONApiResponse(response); + } else { + return new runtime.TextApiResponse(response) as any; + } } /** @@ -707,7 +706,7 @@ export class DefaultApi extends runtime.BaseAPI { async coderouterApiSandboxFileWatchPost( requestParameters: CoderouterApiSandboxFileWatchPostOperationRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction, - ): Promise { + ): Promise { const response = await this.coderouterApiSandboxFileWatchPostRaw( requestParameters, initOverrides, diff --git a/packages/code-provider/src/providers/coderouter/codegen/models/index.ts b/packages/code-provider/src/providers/coderouter/codegen/models/index.ts index 98e058b7ae..9fcf9a2656 100644 --- a/packages/code-provider/src/providers/coderouter/codegen/models/index.ts +++ b/packages/code-provider/src/providers/coderouter/codegen/models/index.ts @@ -16,8 +16,6 @@ export * from './CoderouterApiSandboxFileReadPostRequest'; export * from './CoderouterApiSandboxFileRenamePostRequest'; export * from './CoderouterApiSandboxFileStatPost200Response'; export * from './CoderouterApiSandboxFileStatPostRequest'; -export * from './CoderouterApiSandboxFileWatchPost200Response'; -export * from './CoderouterApiSandboxFileWatchPost200ResponseEventsInner'; export * from './CoderouterApiSandboxFileWatchPostRequest'; export * from './CoderouterApiSandboxFileWritePostRequest'; export * from './CoderouterApiSandboxFileWritePostRequestFilesInner'; diff --git a/packages/code-provider/src/providers/coderouter/index.ts b/packages/code-provider/src/providers/coderouter/index.ts index d30e7ee584..92153e4efb 100644 --- a/packages/code-provider/src/providers/coderouter/index.ts +++ b/packages/code-provider/src/providers/coderouter/index.ts @@ -413,63 +413,55 @@ export class CoderouterFileWatcher extends ProviderFileWatcher { this.off = false; - const tick = async () => { - if (this.off) { - return; - } - - if (this.abortController) { - this.abortController.abort(); - } - - this.abortController = new AbortController(); - - try { - const body = await this.api.coderouterApiSandboxFileWatchPost( - { - coderouterApiSandboxFileWatchPostRequest: { - path: input.args.path, - recursive: input.args.recursive ?? true, - excludePaths: input.args.excludes ?? [], - }, - }, - { - ...this.requestInitOverrides(), - signal: this.abortController?.signal, - }, - ); - - // convert the events to a map of type to paths as the defined by the product - const events = body.events.reduce( - (acc, event) => { - acc[event.type] = [...(acc[event.type] || []), event.path]; - return acc; - }, - {} as Record, - ); - - for (const [type, paths] of Object.entries(events)) { - this.callbacks.forEach(async (callback) => { - await callback({ - type: - type === 'create' ? 'add' : type === 'delete' ? 'remove' : 'change', - paths, - }); - }); - } - } catch (err) { - console.error('[coderouter] poll error', err); - } finally { - // schedule next poll immediately - setTimeout(tick, 200); - } - }; - - tick(); + fetchEventSource( + `${this.api['configuration'].basePath}/coderouter/api/sandbox/file/watch`, + { + ...(this.requestInitOverrides() as Record), + method: 'POST', + body: JSON.stringify({ + path: input.args.path, + recursive: input.args.recursive ?? true, + excludePaths: input.args.excludes ?? [], + }), + signal: this.abortController?.signal, + onmessage: (event) => { + if (event.event === 'message') { + const res = JSON.parse(event.data); + // convert the events to a map of type to paths as the defined by the product + const events = (res.events as Array<{ path: string; type: string }>).reduce( + (acc, event) => { + acc[event.type] = [...(acc[event.type] || []), event.path]; + return acc; + }, + {} as Record, + ); + + for (const [type, paths] of Object.entries(events)) { + this.callbacks.forEach(async (callback) => { + await callback({ + type: + type === 'create' + ? 'add' + : type === 'delete' + ? 'remove' + : 'change', + paths, + }); + }); + } + this.callbacks.forEach((callback) => callback(res.output)); + } + }, + onerror: (err) => { + console.error('[coderouter/file/watch] SSE error', err); + }, + }, + ); } async stop(): Promise { this.off = true; + this.abortController?.abort(); } registerEventCallback(callback: (event: WatchEvent) => Promise): void { @@ -527,10 +519,10 @@ export class CoderouterTerminal extends ProviderTerminal { `${this.api['configuration'].basePath}/coderouter/api/sandbox/terminal/open?terminalId=${this._id}`, { ...(this.requestInitOverrides() as Record), + signal: this.abortController?.signal, onmessage: (event) => { if (event.event === 'message') { const res = JSON.parse(event.data); - console.log('res', res); this.callbacks.forEach((callback) => callback(res.output)); } }, @@ -540,46 +532,6 @@ export class CoderouterTerminal extends ProviderTerminal { }, ); - // this.off = false; - - // const tick = async () => { - // if (this.off) { - // return ''; - // } - - // if (this.abortController) { - // this.abortController.abort(); - // } - - // this.abortController = new AbortController(); - - // let output = ''; - // try { - // const body = await this.api.coderouterApiSandboxTerminalOpenGet( - // { - // terminalId: this._id, - // }, - // { - // ...this.requestInitOverrides(), - // signal: this.abortController?.signal, - // }, - // ); - - // this.callbacks.forEach((callback) => { - // callback(body.output); - // }); - - // output = body.output; - // } catch (err) { - // console.error('[coderouter] poll error', err); - // } finally { - // // schedule next poll immediately - // setTimeout(tick, 200); - // } - // return output; - // }; - - // tick(); return ''; } @@ -608,6 +560,7 @@ export class CoderouterTerminal extends ProviderTerminal { } async kill(): Promise { + this.abortController?.abort(); await this.api.coderouterApiSandboxTerminalKillPost( { coderouterApiSandboxTerminalKillPostRequest: { @@ -620,7 +573,9 @@ export class CoderouterTerminal extends ProviderTerminal { onOutput(callback: (data: string) => void): () => void { this.callbacks.push(callback); - return () => null; + return () => { + this.callbacks = this.callbacks.filter((callback) => callback !== callback); + }; } } From 565422cdd365de39630d694c2dde9b2c9ee428f7 Mon Sep 17 00:00:00 2001 From: Thomas Potaire Date: Thu, 18 Sep 2025 18:22:16 -0700 Subject: [PATCH 5/5] codesandbox watch --- apps/coderouter/.env.example | 3 +- apps/coderouter/README.md | 9 +- apps/coderouter/bun.lock | 43 +- apps/coderouter/package.json | 1 + apps/coderouter/src/api/auth/sign/index.ts | 120 ++--- .../src/api/sandbox/create/index.ts | 117 +++-- .../src/api/sandbox/file/list/index.ts | 83 ++-- apps/coderouter/src/middleware/client.ts | 66 ++- .../src/provider/codesandbox/index.ts | 21 + .../codesandbox/sandbox/file/index.test.ts | 462 ++++++++++++++++++ .../codesandbox/sandbox/file/index.ts | 313 ++++++++++++ .../src/provider/codesandbox/sandbox/index.ts | 126 +++++ .../codesandbox/sandbox/terminal/index.ts | 276 +++++++++++ .../src/provider/e2b/sandbox/index.ts | 2 +- apps/docker-compose.yaml | 5 + .../store/editor/sandbox/session.ts | 5 +- bun.lock | 1 + .../src/providers/coderouter/index.ts | 1 + 18 files changed, 1456 insertions(+), 198 deletions(-) create mode 100644 apps/coderouter/src/provider/codesandbox/index.ts create mode 100644 apps/coderouter/src/provider/codesandbox/sandbox/file/index.test.ts create mode 100644 apps/coderouter/src/provider/codesandbox/sandbox/file/index.ts create mode 100644 apps/coderouter/src/provider/codesandbox/sandbox/index.ts create mode 100644 apps/coderouter/src/provider/codesandbox/sandbox/terminal/index.ts diff --git a/apps/coderouter/.env.example b/apps/coderouter/.env.example index b639a51362..88f96942a7 100644 --- a/apps/coderouter/.env.example +++ b/apps/coderouter/.env.example @@ -8,4 +8,5 @@ JWT_SECRET_KEY="Replace this with your own secret" # It'll be used for server-to-server authentication. CODEROUTER_API_KEY="" -E2B_API_KEY="Get your API key at https://e2b.dev" \ No newline at end of file +E2B_API_KEY="" +CSB_API_KEY="" \ No newline at end of file diff --git a/apps/coderouter/README.md b/apps/coderouter/README.md index 0149c72eb6..0cd8b600d4 100644 --- a/apps/coderouter/README.md +++ b/apps/coderouter/README.md @@ -1,3 +1,5 @@ +YOOOOOBLAHHHHHH + # Bun + TypeScript API Starter (Hono + Drizzle) A batteries-included starter for building an API with **Bun**, **TypeScript**, **Hono**, and **Drizzle ORM**. @@ -19,6 +21,7 @@ OpenAPI: http://localhost:3000/openapi.json Docs (Swagger UI): http://localhost:3000/docs ### Scripts + - `bun dev` — run in watch mode - `bun start` — production start - `bun test` — run all tests with coverage @@ -30,17 +33,21 @@ Docs (Swagger UI): http://localhost:3000/docs - `bun docs` — build TypeDoc to `site/typedoc` ### Multi-DB + Set `DRIZZLE_DB` to `postgres` | `mysql` | `sqlite` and provide `DATABASE_URL` accordingly. SQLite works out-of-the-box (`DATABASE_URL=file:./dev.sqlite`). ### GitHub Pages (Docs) + The `pages.yml` workflow builds and deploys: + - `/openapi.json` -> `/site/api/openapi.json` - Swagger UI -> `/site/api/` - TypeDoc -> `/site/typedoc/` -A small `site/index.html` links to both. + A small `site/index.html` links to both. Enable Pages in **Settings → Pages** (source: GitHub Actions). ## License + MIT diff --git a/apps/coderouter/bun.lock b/apps/coderouter/bun.lock index 4d3f68d91b..9c6855bd8b 100644 --- a/apps/coderouter/bun.lock +++ b/apps/coderouter/bun.lock @@ -4,6 +4,7 @@ "": { "name": "coderouter", "dependencies": { + "@codesandbox/sdk": "^1.1.6", "@e2b/code-interpreter": "2.0.0", "@hono/zod-openapi": "^1.1.0", "@hono/zod-validator": "^0.2.1", @@ -52,6 +53,8 @@ "@bufbuild/protobuf": ["@bufbuild/protobuf@2.7.0", "", {}, "sha512-qn6tAIZEw5i/wiESBF4nQxZkl86aY4KoO0IkUa2Lh+rya64oTOdJQFlZuMwI1Qz9VBJQrQC4QlSA2DNek5gCOA=="], + "@codesandbox/sdk": ["@codesandbox/sdk@1.1.6", "", { "dependencies": { "ora": "^8.2.0", "readline": "^1.3.0" }, "bin": { "csb": "dist/bin/codesandbox.cjs" } }, "sha512-65k6TZSr1c0u8xYjBsz32xhMEq1TwHbqRONJ8h6e3re01qylZ5BDtAU6Nnxr9GQmmhOA0wl+3c4h4zY3/HdHHg=="], + "@connectrpc/connect": ["@connectrpc/connect@2.0.0-rc.3", "", { "peerDependencies": { "@bufbuild/protobuf": "^2.2.0" } }, "sha512-ARBt64yEyKbanyRETTjcjJuHr2YXorzQo0etyS5+P6oSeW8xEuzajA9g+zDnMcj1hlX2dQE93foIWQGfpru7gQ=="], "@connectrpc/connect-web": ["@connectrpc/connect-web@2.0.0-rc.3", "", { "peerDependencies": { "@bufbuild/protobuf": "^2.2.0", "@connectrpc/connect": "2.0.0-rc.3" } }, "sha512-w88P8Lsn5CCsA7MFRl2e6oLY4J/5toiNtJns/YJrlyQaWOy3RO8pDgkz+iIkG98RPMhj2thuBvsd3Cn4DKKCkw=="], @@ -194,12 +197,18 @@ "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], + "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], "character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], + "cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="], + + "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], @@ -252,7 +261,7 @@ "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], - "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + "emoji-regex": ["emoji-regex@10.5.0", "", {}, "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg=="], "emoji-regex-xs": ["emoji-regex-xs@1.0.0", "", {}, "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg=="], @@ -290,6 +299,8 @@ "generate-function": ["generate-function@2.3.1", "", { "dependencies": { "is-property": "^1.0.2" } }, "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ=="], + "get-east-asian-width": ["get-east-asian-width@1.3.1", "", {}, "sha512-R1QfovbPsKmosqTnPoRFiJ7CF9MLRgb53ChvMZm+r4p76/+8yKDy17qLL2PKInORy2RkZZekuK0efYgmzTkXyQ=="], + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], @@ -326,8 +337,12 @@ "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + "is-interactive": ["is-interactive@2.0.0", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="], + "is-property": ["is-property@1.0.2", "", {}, "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="], + "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], "jackspeak": ["jackspeak@4.1.1", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" } }, "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ=="], @@ -354,6 +369,8 @@ "lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="], + "log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="], + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], "lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], @@ -388,6 +405,8 @@ "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], + "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], "minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], @@ -416,6 +435,8 @@ "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + "onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], + "oniguruma-to-es": ["oniguruma-to-es@2.3.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.1.1", "regex-recursion": "^5.1.1" } }, "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g=="], "openapi-fetch": ["openapi-fetch@0.9.8", "", { "dependencies": { "openapi-typescript-helpers": "^0.0.8" } }, "sha512-zM6elH0EZStD/gSiNlcPrzXcVQ/pZo3BDvC6CDwRDUt1dDzxlshpmQnpD6cZaJ39THaSmwVCxxRrPKNM1hHrDg=="], @@ -424,6 +445,8 @@ "openapi3-ts": ["openapi3-ts@4.5.0", "", { "dependencies": { "yaml": "^2.8.0" } }, "sha512-jaL+HgTq2Gj5jRcfdutgRGLosCy/hT8sQf6VOy+P+g36cZOjI1iukdPnijC+4CmeRzg/jEllJUboEic2FhxhtQ=="], + "ora": ["ora@8.2.0", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="], + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], @@ -470,6 +493,8 @@ "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + "readline": ["readline@1.3.0", "", {}, "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg=="], + "regex": ["regex@5.1.1", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw=="], "regex-recursion": ["regex-recursion@5.1.1", "", { "dependencies": { "regex": "^5.1.1", "regex-utilities": "^2.3.0" } }, "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w=="], @@ -478,6 +503,8 @@ "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + "restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], @@ -516,7 +543,9 @@ "sqlstring": ["sqlstring@2.3.3", "", {}, "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg=="], - "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + "stdin-discarder": ["stdin-discarder@0.2.2", "", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="], + + "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -598,8 +627,12 @@ "@hono/zod-openapi/@hono/zod-validator": ["@hono/zod-validator@0.7.2", "", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-ub5eL/NeZ4eLZawu78JpW/J+dugDAYhwqUIdp9KYScI6PZECij4Hx4UsrthlEUutqDDhPwRI0MscUfNkvn/mqQ=="], + "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + "glob/minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="], + "log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="], + "path-scurry/lru-cache": ["lru-cache@11.2.1", "", {}, "sha512-r8LA6i4LP4EeWOhqBaZZjDWwehd1xUJPCJd9Sv300H0ZmcUER4+JPh7bqqZeqs1o5pgtgvXm+d9UGrB5zZGDiQ=="], "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -610,6 +643,8 @@ "tar-fs/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], + "wrap-ansi/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -660,10 +695,14 @@ "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], + "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "wrap-ansi/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], } } diff --git a/apps/coderouter/package.json b/apps/coderouter/package.json index 8a3296a463..d3d088d8cc 100644 --- a/apps/coderouter/package.json +++ b/apps/coderouter/package.json @@ -13,6 +13,7 @@ "db:migrate": "bun run scripts/migrate.ts" }, "dependencies": { + "@codesandbox/sdk": "^1.1.6", "@e2b/code-interpreter": "2.0.0", "@hono/zod-openapi": "^1.1.0", "@hono/zod-validator": "^0.2.1", diff --git a/apps/coderouter/src/api/auth/sign/index.ts b/apps/coderouter/src/api/auth/sign/index.ts index 9786138862..98d2e7d5c3 100644 --- a/apps/coderouter/src/api/auth/sign/index.ts +++ b/apps/coderouter/src/api/auth/sign/index.ts @@ -1,81 +1,81 @@ -import { LocalHono } from "@/server"; -import { encodeJwtToken, verifyApiKeyFromHeader } from "@/util/auth"; -import { createRoute, z } from "@hono/zod-openapi"; -import { env } from "bun"; +import { LocalHono } from '@/server'; +import { encodeJwtToken, verifyApiKeyFromHeader } from '@/util/auth'; +import { createRoute, z } from '@hono/zod-openapi'; +import { env } from 'bun'; export interface AuthSignInput { - sandboxId?: string; - userId?: string; + sandboxId?: string; + userId?: string; } const BodySchema: z.ZodType = z.object({ - sandboxId: z.string().optional().openapi({ - description: - "The ID of the sandbox. This is your own ID. It can be a UUID or any unique string. Required when using a sandbox.", - example: "00000000-0000-0000-0000-000000000000", - }), - userId: z.string().optional().openapi({ - description: - "The ID of the user. This is your own ID. It can be a UUID or any unique string.", - example: "00000000-0000-0000-0000-000000000000", - }), + sandboxId: z.string().optional().openapi({ + description: + 'The ID of the sandbox. This is your own ID. It can be a UUID or any unique string. Required when using a sandbox.', + example: '00000000-0000-0000-0000-000000000000', + }), + userId: z.string().optional().openapi({ + description: + 'The ID of the user. This is your own ID. It can be a UUID or any unique string.', + example: '00000000-0000-0000-0000-000000000000', + }), }); const ResponseSchema = z.object({ - jwt: z.string().openapi({ - description: `The JWT token to send when interacting with the API as header "X-Auth-Jwt.".`, - }), + jwt: z.string().openapi({ + description: `The JWT token to send when interacting with the API as header "X-Auth-Jwt.".`, + }), }); const route = createRoute({ - method: "post", - path: env.URL_PATH_PREFIX + "/api/auth/sign", - security: [{ apikey: [] }], - request: { - body: { - content: { - "application/json": { - schema: BodySchema, + method: 'post', + path: env.URL_PATH_PREFIX + '/api/auth/sign', + security: [{ apikey: [] }], + request: { + body: { + content: { + 'application/json': { + schema: BodySchema, + }, + }, }, - }, }, - }, - responses: { - 200: { - content: { - "application/json": { - schema: ResponseSchema, + responses: { + 200: { + content: { + 'application/json': { + schema: ResponseSchema, + }, + }, + description: + 'Create a new sandbox. If the sandbox already exists, the system will resume the sandbox.', }, - }, - description: - "Create a new sandbox. If the sandbox already exists, the system will resume the sandbox.", - }, - 401: { - content: { - "application/json": { - schema: z.object({ error: z.string() }), + 401: { + content: { + 'application/json': { + schema: z.object({ error: z.string() }), + }, + }, + description: 'The API key is invalid.', }, - }, - description: "The API key is invalid.", }, - }, }); export function api_auth_sign(app: LocalHono) { - app.openapi(route, async (c) => { - if (!verifyApiKeyFromHeader(c.req.header("Authorization"))) { - return c.json({ error: "Unauthorized" }, 401); - } - const body = await c.req.valid("json"); - const jwt = encodeJwtToken({ - sandboxId: body.sandboxId, - userId: body.userId, + app.openapi(route, async (c) => { + if (!verifyApiKeyFromHeader(c.req.header('Authorization'))) { + return c.json({ error: 'Unauthorized' }, 401); + } + const body = await c.req.valid('json'); + const jwt = encodeJwtToken({ + sandboxId: body.sandboxId, + userId: body.userId, + }); + return c.json( + { + jwt, + }, + 200, + ); }); - return c.json( - { - jwt, - }, - 200 - ); - }); } diff --git a/apps/coderouter/src/api/sandbox/create/index.ts b/apps/coderouter/src/api/sandbox/create/index.ts index e37b348108..06bf6f9e3a 100644 --- a/apps/coderouter/src/api/sandbox/create/index.ts +++ b/apps/coderouter/src/api/sandbox/create/index.ts @@ -1,76 +1,73 @@ -import { ClientError, ClientErrorCode } from "@/provider/definition"; -import { SandboxCreateInput } from "@/provider/definition/sandbox"; -import { LocalHono } from "@/server"; -import { JwtAuthResponses } from "@/util/auth"; -import { createRoute, z } from "@hono/zod-openapi"; -import { env } from "bun"; +import { ClientError, ClientErrorCode } from '@/provider/definition'; +import { SandboxCreateInput } from '@/provider/definition/sandbox'; +import { LocalHono } from '@/server'; +import { JwtAuthResponses } from '@/util/auth'; +import { createRoute, z } from '@hono/zod-openapi'; +import { env } from 'bun'; const BodySchema: z.ZodType = z.object({ - templateId: z.string().openapi({ - description: "The ID of the template to use for the sandbox.", - example: "00000000-0000-0000-0000-000000000000", - }), - metadata: z.record(z.string(), z.string()).openapi({ - description: "The metadata of the sandbox.", - }), + templateId: z.string().openapi({ + description: 'The ID of the template to use for the sandbox.', + example: '00000000-0000-0000-0000-000000000000', + }), + metadata: z.record(z.string(), z.string()).openapi({ + description: 'The metadata of the sandbox.', + }), }); const ResponseSchema = z.object({ - id: z.string().openapi({ - description: - "The ID of the sandbox. This is your own ID. It can be a UUID or any unique string.", - example: "00000000-0000-0000-0000-000000000000", - }), + id: z.string().openapi({ + description: + 'The ID of the sandbox. This is your own ID. It can be a UUID or any unique string.', + example: '00000000-0000-0000-0000-000000000000', + }), }); const route = createRoute({ - method: "post", - path: env.URL_PATH_PREFIX + "/api/sandbox/create", - security: [{ jwt: [] }], - request: { - body: { - content: { - "application/json": { - schema: BodySchema, + method: 'post', + path: env.URL_PATH_PREFIX + '/api/sandbox/create', + security: [{ jwt: [] }], + request: { + body: { + content: { + 'application/json': { + schema: BodySchema, + }, + }, }, - }, }, - }, - responses: { - 200: { - content: { - "application/json": { - schema: ResponseSchema, + responses: { + 200: { + content: { + 'application/json': { + schema: ResponseSchema, + }, + }, + description: + 'Create a new sandbox. If the sandbox already exists, the system will resume the sandbox.', }, - }, - description: - "Create a new sandbox. If the sandbox already exists, the system will resume the sandbox.", + ...JwtAuthResponses, }, - ...JwtAuthResponses, - }, }); export function api_sandbox_create(app: LocalHono) { - app.openapi(route, async (c) => { - const body = await c.req.valid("json"); - try { - await c.get("client").sandbox.get({}); - await c.get("client").sandbox.resume({}); - } catch (e) { - if ( - e instanceof ClientError && - e.code === ClientErrorCode.SandboxNotFound - ) { - await c.get("client").sandbox.create(body); - } else { - throw e; - } - } - return c.json( - { - id: c.get("auth").sandboxId!, - }, - 200 - ); - }); + app.openapi(route, async (c) => { + const body = await c.req.valid('json'); + try { + await c.get('client').sandbox.get({}); + await c.get('client').sandbox.resume({}); + } catch (e) { + if (e instanceof ClientError && e.code === ClientErrorCode.SandboxNotFound) { + await c.get('client').sandbox.create(body); + } else { + throw e; + } + } + return c.json( + { + id: c.get('auth').sandboxId!, + }, + 200, + ); + }); } diff --git a/apps/coderouter/src/api/sandbox/file/list/index.ts b/apps/coderouter/src/api/sandbox/file/list/index.ts index 5b6d8b6d3c..40dbf08592 100644 --- a/apps/coderouter/src/api/sandbox/file/list/index.ts +++ b/apps/coderouter/src/api/sandbox/file/list/index.ts @@ -1,59 +1,56 @@ -import { - SandboxFileListInput, - SandboxFileListOutput, -} from "@/provider/definition/sandbox/file"; -import { LocalHono } from "@/server"; -import { JwtAuthResponses } from "@/util/auth"; -import { createRoute, z } from "@hono/zod-openapi"; -import { env } from "bun"; +import { SandboxFileListInput, SandboxFileListOutput } from '@/provider/definition/sandbox/file'; +import { LocalHono } from '@/server'; +import { JwtAuthResponses } from '@/util/auth'; +import { createRoute, z } from '@hono/zod-openapi'; +import { env } from 'bun'; const BodySchema: z.ZodType = z.object({ - path: z.string().openapi({ - description: "The path of the directory to list.", - example: "/path/to/directory", - }), + path: z.string().openapi({ + description: 'The path of the directory to list.', + example: '/path/to/directory', + }), }); const ResponseSchema: z.ZodType = z.object({ - files: z.array( - z.object({ - name: z.string(), - path: z.string(), - type: z.enum(["file", "directory"]), - }) - ), + files: z.array( + z.object({ + name: z.string(), + path: z.string(), + type: z.enum(['file', 'directory']), + }), + ), }); const route = createRoute({ - method: "post", - path: env.URL_PATH_PREFIX + "/api/sandbox/file/list", - security: [{ jwt: [] }], - request: { - body: { - content: { - "application/json": { - schema: BodySchema, + method: 'post', + path: env.URL_PATH_PREFIX + '/api/sandbox/file/list', + security: [{ jwt: [] }], + request: { + body: { + content: { + 'application/json': { + schema: BodySchema, + }, + }, }, - }, }, - }, - responses: { - 200: { - content: { - "application/json": { - schema: ResponseSchema, + responses: { + 200: { + content: { + 'application/json': { + schema: ResponseSchema, + }, + }, + description: 'List files in the sandbox.', }, - }, - description: "List files in the sandbox.", + ...JwtAuthResponses, }, - ...JwtAuthResponses, - }, }); export function api_sandbox_file_list(app: LocalHono) { - app.openapi(route, async (c) => { - const body = await c.req.valid("json"); - const result = await c.get("client").sandbox.file.list(body); - return c.json(result, 200); - }); + app.openapi(route, async (c) => { + const body = await c.req.valid('json'); + const result = await c.get('client').sandbox.file.list(body); + return c.json(result, 200); + }); } diff --git a/apps/coderouter/src/middleware/client.ts b/apps/coderouter/src/middleware/client.ts index cd4f63bd26..d4f0a0ecf6 100644 --- a/apps/coderouter/src/middleware/client.ts +++ b/apps/coderouter/src/middleware/client.ts @@ -1,29 +1,43 @@ -import { E2BClient } from "@/provider/e2b"; -import { env } from "bun"; -import { LocalHono } from "@/server"; +import { E2BClient } from '@/provider/e2b'; +import { env } from 'bun'; +import { LocalHono } from '@/server'; +import { CodesandboxClient } from '@/provider/codesandbox'; export function setupClientMiddleware(app: LocalHono, path: string) { - return app.use(path, async (c, next) => { - if (env.E2B_API_KEY) { - const auth = c.get("auth"); - c.set( - "client", - new E2BClient( - { - sandboxId: auth?.sandboxId, - userId: auth?.userId, - }, - env.E2B_API_KEY - ) - ); - await next(); - } else { - return c.json( - { - error: `"E2B_API_KEY" is not set`, - }, - 500 - ); - } - }); + return app.use(path, async (c, next) => { + if (env.E2B_API_KEY) { + const auth = c.get('auth'); + c.set( + 'client', + new E2BClient( + { + sandboxId: auth?.sandboxId, + userId: auth?.userId, + }, + env.E2B_API_KEY, + ), + ); + await next(); + } else if (env.CSB_API_KEY) { + const auth = c.get('auth'); + c.set( + 'client', + new CodesandboxClient( + { + sandboxId: auth?.sandboxId, + userId: auth?.userId, + }, + env.CSB_API_KEY, + ), + ); + await next(); + } else { + return c.json( + { + error: `"E2B_API_KEY" is not set`, + }, + 500, + ); + } + }); } diff --git a/apps/coderouter/src/provider/codesandbox/index.ts b/apps/coderouter/src/provider/codesandbox/index.ts new file mode 100644 index 0000000000..27c44e5a71 --- /dev/null +++ b/apps/coderouter/src/provider/codesandbox/index.ts @@ -0,0 +1,21 @@ +import { CodeSandbox, RestSession, Sandbox } from '@codesandbox/sdk'; +import { Client, ClientOptions } from '../definition'; +import { CodesandboxSandbox } from './sandbox'; + +export class CodesandboxClient extends Client { + public readonly sandbox: CodesandboxSandbox; + + // property is initialized by the sandbox class + // this is the Codesandbox sandbox instance + _sdk?: CodeSandbox; + _sandbox?: Sandbox; + _client?: RestSession; + + constructor( + options: ClientOptions, + public readonly apiKey: string, + ) { + super(options); + this.sandbox = new CodesandboxSandbox(this); + } +} diff --git a/apps/coderouter/src/provider/codesandbox/sandbox/file/index.test.ts b/apps/coderouter/src/provider/codesandbox/sandbox/file/index.test.ts new file mode 100644 index 0000000000..a0442ee119 --- /dev/null +++ b/apps/coderouter/src/provider/codesandbox/sandbox/file/index.test.ts @@ -0,0 +1,462 @@ +import { describe, expect, it, beforeEach, jest } from 'bun:test'; +import { CodesandboxClient } from '@/provider/e2b/index'; +import { CodesandboxSandboxFile } from './index'; +import { ClientError, ClientErrorCode, ClientOptions } from '@/provider/definition'; + +// Create a mock sandbox object that matches the expected interface +const createMockSandbox = () => + ({ + files: { + exists: jest.fn(), + read: jest.fn(), + write: jest.fn(), + remove: jest.fn(), + rename: jest.fn(), + list: jest.fn(), + getInfo: jest.fn(), + }, + create: jest.fn(), + kill: jest.fn(), + // Add other required properties to satisfy the Sandbox interface + runCode: jest.fn(), + createCodeContext: jest.fn(), + jupyterUrl: 'http://localhost:8888', + commands: jest.fn(), + sandboxId: 'test-sandbox-id', + process: { pid: 123 }, + close: jest.fn(), + restart: jest.fn(), + getLogs: jest.fn(), + getProcessLogs: jest.fn(), + getStdout: jest.fn(), + getStderr: jest.fn(), + getExitCode: jest.fn(), + getStatus: jest.fn(), + getMetadata: jest.fn(), + updateMetadata: jest.fn(), + getEnvironmentVariables: jest.fn(), + updateEnvironmentVariables: jest.fn(), + getPorts: jest.fn(), + getOpenPorts: jest.fn(), + getOpenPort: jest.fn(), + getPort: jest.fn(), + openPort: jest.fn(), + closePort: jest.fn(), + getHostname: jest.fn(), + getUrl: jest.fn(), + getLocalUrl: jest.fn(), + getPublicUrl: jest.fn(), + getLocalPort: jest.fn(), + getPublicPort: jest.fn(), + getLocalHostname: jest.fn(), + getPublicHostname: jest.fn(), + getLocalProtocol: jest.fn(), + getPublicProtocol: jest.fn(), + getLocalScheme: jest.fn(), + getPublicScheme: jest.fn(), + getLocalAuthority: jest.fn(), + getPublicAuthority: jest.fn(), + getLocalPath: jest.fn(), + getPublicPath: jest.fn(), + getLocalQuery: jest.fn(), + getPublicQuery: jest.fn(), + getLocalFragment: jest.fn(), + getPublicFragment: jest.fn(), + getLocalUserInfo: jest.fn(), + getPublicUserInfo: jest.fn(), + getLocalUsername: jest.fn(), + getPublicUsername: jest.fn(), + getLocalPassword: jest.fn(), + getPublicPassword: jest.fn(), + getLocalHost: jest.fn(), + getPublicHost: jest.fn(), + getLocalPortNumber: jest.fn(), + getPublicPortNumber: jest.fn(), + getLocalHostnameString: jest.fn(), + getPublicHostnameString: jest.fn(), + getLocalProtocolString: jest.fn(), + getPublicProtocolString: jest.fn(), + getLocalSchemeString: jest.fn(), + getPublicSchemeString: jest.fn(), + getLocalAuthorityString: jest.fn(), + getPublicAuthorityString: jest.fn(), + getLocalPathString: jest.fn(), + getPublicPathString: jest.fn(), + getLocalQueryString: jest.fn(), + getPublicQueryString: jest.fn(), + getLocalFragmentString: jest.fn(), + getPublicFragmentString: jest.fn(), + getLocalUserInfoString: jest.fn(), + getPublicUserInfoString: jest.fn(), + getLocalUsernameString: jest.fn(), + getPublicUsernameString: jest.fn(), + getLocalPasswordString: jest.fn(), + getPublicPasswordString: jest.fn(), + getLocalHostString: jest.fn(), + getPublicHostString: jest.fn(), + getLocalPortNumberString: jest.fn(), + getPublicPortNumberString: jest.fn(), + getLocalHostnameNumber: jest.fn(), + getPublicHostnameNumber: jest.fn(), + getLocalProtocolNumber: jest.fn(), + getPublicProtocolNumber: jest.fn(), + getLocalSchemeNumber: jest.fn(), + getPublicSchemeNumber: jest.fn(), + getLocalAuthorityNumber: jest.fn(), + getPublicAuthorityNumber: jest.fn(), + getLocalPathNumber: jest.fn(), + getPublicPathNumber: jest.fn(), + getLocalQueryNumber: jest.fn(), + getPublicQueryNumber: jest.fn(), + getLocalFragmentNumber: jest.fn(), + getPublicFragmentNumber: jest.fn(), + getLocalUserInfoNumber: jest.fn(), + getPublicUserInfoNumber: jest.fn(), + getLocalUsernameNumber: jest.fn(), + getPublicUsernameNumber: jest.fn(), + getLocalPasswordNumber: jest.fn(), + getPublicPasswordNumber: jest.fn(), + getLocalHostNumber: jest.fn(), + getPublicHostNumber: jest.fn(), + getLocalPortNumberNumber: jest.fn(), + getPublicPortNumberNumber: jest.fn(), + }) as any; + +class CodesandboxMockClient extends CodesandboxClient { + private _mockSandbox = createMockSandbox(); + + constructor(options: ClientOptions) { + super(options, 'test-api-key'); + } + + _sandbox = this._mockSandbox; + + // Method to set sandbox to null for testing error cases + setSandboxNull() { + this._mockSandbox = null as any; + this._sandbox = null as any; + } + + // Method to restore sandbox for testing + restoreSandbox() { + this._mockSandbox = createMockSandbox(); + this._sandbox = this._mockSandbox; + } +} + +describe('CodesandboxSandboxFile', () => { + let mockClient: CodesandboxMockClient; + let sandboxFile: CodesandboxSandboxFile; + + beforeEach(() => { + mockClient = new CodesandboxMockClient({ sandboxId: 'test-sandbox-id' }); + sandboxFile = new CodesandboxSandboxFile(mockClient); + jest.clearAllMocks(); + }); + + describe('copy', () => { + it('should throw Unimplemented error', async () => { + await expect(sandboxFile.copy({ oldPath: '/old', newPath: '/new' })).rejects.toThrow( + new ClientError(ClientErrorCode.Unimplemented, 'Not implemented', false), + ); + }); + }); + + describe('delete', () => { + it('should delete file successfully', async () => { + const input = { path: '/test/file.txt' }; + + await sandboxFile.delete(input); + + expect(mockClient._sandbox.files.remove).toHaveBeenCalledTimes(1); + expect(mockClient._sandbox.files.remove).toHaveBeenCalledWith('/test/file.txt'); + }); + + it('should throw error when sandbox is not instantiated', async () => { + mockClient.setSandboxNull(); + + const input = { path: '/test/file.txt' }; + + await expect(sandboxFile.delete(input)).rejects.toThrow( + new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ), + ); + + // Note: We can't check mockClient._sandbox.files.remove since sandbox is null + }); + }); + + describe('download', () => { + it('should throw Unimplemented error', async () => { + await expect(sandboxFile.download({ path: '/test/file.txt' })).rejects.toThrow( + new ClientError(ClientErrorCode.Unimplemented, 'Not implemented', false), + ); + }); + }); + + describe('list', () => { + it('should list files successfully', async () => { + const mockFiles = [ + { name: 'file1.txt', path: '/dir/file1.txt', type: 'file' }, + { name: 'subdir', path: '/dir/subdir', type: 'directory' }, + ]; + + (mockClient._sandbox.files.list as any).mockResolvedValue(mockFiles); + + const input = { path: '/dir' }; + const result = await sandboxFile.list(input); + + expect(mockClient._sandbox.files.list).toHaveBeenCalledTimes(1); + expect(mockClient._sandbox.files.list).toHaveBeenCalledWith('/dir'); + expect(result).toEqual({ + files: [ + { name: 'file1.txt', path: '/dir/file1.txt', type: 'file' }, + { name: 'subdir', path: '/dir/subdir', type: 'directory' }, + ], + }); + }); + + it('should throw error when sandbox is not instantiated', async () => { + mockClient.setSandboxNull(); + + const input = { path: '/dir' }; + + await expect(sandboxFile.list(input)).rejects.toThrow( + new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ), + ); + }); + }); + + describe('read', () => { + it('should read file successfully', async () => { + const mockData = 'file content'; + (mockClient._sandbox.files.read as any).mockResolvedValue(mockData); + + const input = { path: '/test/file.txt' }; + const result = await sandboxFile.read(input); + + expect(mockClient._sandbox.files.read).toHaveBeenCalledTimes(1); + expect(mockClient._sandbox.files.read).toHaveBeenCalledWith('/test/file.txt'); + expect(result).toEqual({ data: mockData }); + }); + + it("should throw FileNotFound error when file doesn't exist", async () => { + (mockClient._sandbox.files.read as any).mockResolvedValue(null); + + const input = { path: '/test/file.txt' }; + + await expect(sandboxFile.read(input)).rejects.toThrow( + new ClientError(ClientErrorCode.FileNotFound, 'File not found', false), + ); + + expect(mockClient._sandbox.files.read).toHaveBeenCalledTimes(1); + expect(mockClient._sandbox.files.read).toHaveBeenCalledWith('/test/file.txt'); + }); + + it('should throw error when sandbox is not instantiated', async () => { + mockClient.setSandboxNull(); + + const input = { path: '/test/file.txt' }; + + await expect(sandboxFile.read(input)).rejects.toThrow( + new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ), + ); + }); + }); + + describe('rename', () => { + it('should rename file successfully', async () => { + const input = { oldPath: '/old/file.txt', newPath: '/new/file.txt' }; + + await sandboxFile.rename(input); + + expect(mockClient._sandbox.files.rename).toHaveBeenCalledTimes(1); + expect(mockClient._sandbox.files.rename).toHaveBeenCalledWith( + '/old/file.txt', + '/new/file.txt', + ); + }); + + it('should throw error when sandbox is not instantiated', async () => { + mockClient.setSandboxNull(); + + const input = { oldPath: '/old/file.txt', newPath: '/new/file.txt' }; + + await expect(sandboxFile.rename(input)).rejects.toThrow( + new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ), + ); + }); + }); + + describe('stat', () => { + it('should get file info successfully for file', async () => { + const mockFileInfo = { type: 'file' }; + (mockClient._sandbox.files.getInfo as any).mockResolvedValue(mockFileInfo); + + const input = { path: '/test/file.txt' }; + const result = await sandboxFile.stat(input); + + expect(mockClient._sandbox.files.getInfo).toHaveBeenCalledTimes(1); + expect(mockClient._sandbox.files.getInfo).toHaveBeenCalledWith('/test/file.txt'); + expect(result).toEqual({ type: 'file' }); + }); + + it('should get file info successfully for directory', async () => { + const mockFileInfo = { type: 'directory' }; + (mockClient._sandbox.files.getInfo as any).mockResolvedValue(mockFileInfo); + + const input = { path: '/test/dir' }; + const result = await sandboxFile.stat(input); + + expect(mockClient._sandbox.files.getInfo).toHaveBeenCalledTimes(1); + expect(mockClient._sandbox.files.getInfo).toHaveBeenCalledWith('/test/dir'); + expect(result).toEqual({ type: 'directory' }); + }); + + it("should throw FileNotFound error when file doesn't exist", async () => { + (mockClient._sandbox.files.getInfo as any).mockResolvedValue(null); + + const input = { path: '/test/file.txt' }; + + await expect(sandboxFile.stat(input)).rejects.toThrow( + new ClientError(ClientErrorCode.FileNotFound, 'File not found', false), + ); + + expect(mockClient._sandbox.files.getInfo).toHaveBeenCalledTimes(1); + expect(mockClient._sandbox.files.getInfo).toHaveBeenCalledWith('/test/file.txt'); + }); + + it('should throw error when sandbox is not instantiated', async () => { + mockClient.setSandboxNull(); + + const input = { path: '/test/file.txt' }; + + await expect(sandboxFile.stat(input)).rejects.toThrow( + new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ), + ); + }); + }); + + describe('write', () => { + it('should write single file successfully', async () => { + const input = { + files: [ + { + path: '/test/file.txt', + data: 'file content', + overwrite: true, + }, + ], + }; + + await sandboxFile.write(input); + + expect(mockClient._sandbox.files.write).toHaveBeenCalledTimes(1); + expect(mockClient._sandbox.files.write).toHaveBeenCalledWith([ + { path: '/test/file.txt', data: 'file content' }, + ]); + }); + + it('should write multiple files successfully', async () => { + const input = { + files: [ + { path: '/test/file1.txt', data: 'content1', overwrite: true }, + { path: '/test/file2.txt', data: 'content2', overwrite: false }, + ], + }; + + await sandboxFile.write(input); + + expect(mockClient._sandbox.files.write).toHaveBeenCalledTimes(1); + expect(mockClient._sandbox.files.write).toHaveBeenCalledWith([ + { path: '/test/file1.txt', data: 'content1' }, + { path: '/test/file2.txt', data: 'content2' }, + ]); + }); + + it('should skip existing files when overwrite is false', async () => { + (mockClient._sandbox.files.exists as any).mockResolvedValue(true); + + const input = { + files: [ + { + path: '/test/file.txt', + data: 'file content', + overwrite: false, + }, + ], + }; + + await sandboxFile.write(input); + + expect(mockClient._sandbox.files.exists).toHaveBeenCalledTimes(1); + expect(mockClient._sandbox.files.exists).toHaveBeenCalledWith('/test/file.txt'); + expect(mockClient._sandbox.files.write).toHaveBeenCalledTimes(1); + expect(mockClient._sandbox.files.write).toHaveBeenCalledWith([]); + }); + + it("should write file when overwrite is false and file doesn't exist", async () => { + (mockClient._sandbox.files.exists as any).mockResolvedValue(false); + + const input = { + files: [ + { + path: '/test/file.txt', + data: 'file content', + overwrite: false, + }, + ], + }; + + await sandboxFile.write(input); + + expect(mockClient._sandbox.files.exists).toHaveBeenCalledTimes(1); + expect(mockClient._sandbox.files.exists).toHaveBeenCalledWith('/test/file.txt'); + expect(mockClient._sandbox.files.write).toHaveBeenCalledTimes(1); + expect(mockClient._sandbox.files.write).toHaveBeenCalledWith([ + { path: '/test/file.txt', data: 'file content' }, + ]); + }); + + it('should throw error when sandbox is not instantiated', async () => { + mockClient.setSandboxNull(); + + const input = { + files: [ + { + path: '/test/file.txt', + data: 'file content', + overwrite: true, + }, + ], + }; + + await expect(sandboxFile.write(input)).rejects.toThrow( + new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ), + ); + }); + }); +}); diff --git a/apps/coderouter/src/provider/codesandbox/sandbox/file/index.ts b/apps/coderouter/src/provider/codesandbox/sandbox/file/index.ts new file mode 100644 index 0000000000..66393504e7 --- /dev/null +++ b/apps/coderouter/src/provider/codesandbox/sandbox/file/index.ts @@ -0,0 +1,313 @@ +import { ClientError, ClientErrorCode } from '@/provider/definition'; +import { CodesandboxClient } from '../..'; +import { + SandboxFile, + SandboxFileCopyInput, + SandboxFileCopyOutput, + SandboxFileDeleteInput, + SandboxFileDeleteOutput, + SandboxFileDownloadInput, + SandboxFileDownloadOutput, + SandboxFileListInput, + SandboxFileListOutput, + SandboxFileReadInput, + SandboxFileReadOutput, + SandboxFileRenameInput, + SandboxFileRenameOutput, + SandboxFileStatInput, + SandboxFileStatOutput, + SandboxFileWriteOutput, + SandboxFileWriteInput, + SandboxFileWatchInput, + SandboxFileWatchOutput, +} from '../../../definition/sandbox/file'; +import { Watcher as CodesandboxWatcher } from '@codesandbox/sdk'; +// import { NotFoundError, WatchHandle } from '@e2b/code-interpreter'; +import path from 'path'; +import { v4 as uuid } from 'uuid'; + +const watchers: Map = new Map(); + +export class CodesandboxSandboxFile extends SandboxFile { + // the folder to store the files in the sandbox + // when creating a new template, the code must be stored in this folder + protected folder: string = '/code'; + + constructor(client: CodesandboxClient) { + super(client); + } + + async copy(input: SandboxFileCopyInput): Promise { + throw new ClientError(ClientErrorCode.Unimplemented, 'Not implemented', false); + } + + async delete(input: SandboxFileDeleteInput): Promise { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ); + } + const client = await this.client._sandbox.connect(); + await client.fs.remove(this.fullpath(input.path)); + return {}; + } + + async download(input: SandboxFileDownloadInput): Promise { + throw new ClientError(ClientErrorCode.Unimplemented, 'Not implemented', false); + } + + async list(input: SandboxFileListInput): Promise { + if (!this.client._client) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Client is not instantiated. Call start() or resume() first.', + false, + ); + } + const client = this.client._client; + const res = await client.fs.readDir({ path: this.fullpath(input.path) }); + return { + files: + res.data?.result?.entries.map((file) => ({ + name: file.name, + path: file.name.replace(this.folder, ''), + type: file.type === 'file' ? 'file' : 'directory', + })) || [], + }; + } + + async read(input: SandboxFileReadInput): Promise { + if (!this.client._client) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Client is not instantiated. Call start() or resume() first.', + false, + ); + } + const client = this.client._client; + const data = await client.fs.readFile({ path: this.fullpath(input.path) }); + if (!data) { + throw new ClientError(ClientErrorCode.FileNotFound, 'File not found', false); + } + + const content = data.data?.result?.content; + if (!content) { + throw new ClientError(ClientErrorCode.FileNotFound, 'File content not found', false); + } + + // Convert Blob or File to string + const contentString = await content.text(); + + return { + data: contentString, + }; + } + + async rename(input: SandboxFileRenameInput): Promise { + if (!this.client._client) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Client is not instantiated. Call start() or resume() first.', + false, + ); + } + const client = this.client._client; + await client.fs.rename({ + from: this.fullpath(input.oldPath), + to: this.fullpath(input.newPath), + }); + return {}; + } + + async stat(input: SandboxFileStatInput): Promise { + if (!this.client._client) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Client is not instantiated. Call start() or resume() first.', + false, + ); + } + const client = this.client._client; + const response = await client.fs.stat({ path: this.fullpath(input.path) }); + if (!response.data?.result) { + throw new ClientError(ClientErrorCode.FileNotFound, 'File not found', false); + } + return { + type: response.data.result.type === 'file' ? 'file' : 'directory', + }; + } + + async write(input: SandboxFileWriteInput): Promise { + if (!this.client._client) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Client is not instantiated. Call start() or resume() first.', + false, + ); + } + const client = this.client._client; + for (const file of Array.isArray(input.files) ? input.files : [input.files]) { + if (!file.overwrite) { + const exists = await this.stat({ path: this.fullpath(file.path) }); + if (exists) { + continue; + } + } + await client.fs.writeFile({ + path: this.fullpath(file.path), + content: new Blob([file.data], { type: 'text/plain' }), + }); + } + + return {}; + } + + async watch(input: SandboxFileWatchInput, onOutput: (output: SandboxFileWatchOutput) => void) { + if (!this.client._client) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Client is not instantiated. Call start() or resume() first.', + false, + ); + } + + const sandboxId = (this.client._client.constructor as any).id; + + if (!sandboxId) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not running. Call start() or resume() first.', + false, + ); + } + + if (!watchers.has(sandboxId)) { + watchers.set(sandboxId, new Watcher(this.client)); + } + + const watcher = watchers.get(sandboxId); + + // obviously this should never happen + if (!watcher) { + throw new ClientError(ClientErrorCode.ImplementationError, 'Watcher not found', false); + } + + if (watcher.off) { + await watcher.start({ + path: this.fullpath(input.path), + recursive: input.recursive, + excludePaths: input.excludePaths, + }); + } + + watcher.onOutput((output) => onOutput(output)); + + return { + close: () => { + watcher.stop(); + }, + }; + } + + protected fullpath(path: string): string { + return this.folder + (path.startsWith('/') ? '' : '/') + path; + } +} + +interface WatcherEvent { + path: string; + type: 'create' | 'update' | 'delete'; +} + +class Watcher { + protected readonly maxEvents = 500; + // longer timeout might lead to gateway timeouts + protected readonly promiseTimeout = 30000; // 30 seconds + protected readonly watchTimeout = 300000; // 5 minutes + + protected events: Array = []; + + protected _off: boolean = true; + protected _watchHandle: any = null; + protected _onEventCallbacks: Array<(output: SandboxFileWatchOutput) => void> = []; + + constructor(protected readonly client: CodesandboxClient) {} + + get off(): boolean { + return this._off; + } + + async start(input: SandboxFileWatchInput): Promise { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ); + } + + if (!this._off) { + return; + } + + this._off = false; + let timeout: NodeJS.Timeout | null = null; + + console.log('starting watch', input); + // Use WebSocket approach + const wsClient = await this.client._sandbox.connect(); + console.log('wsClient', wsClient); + + this._watchHandle = await wsClient.fs.watch(input.path, { + recursive: input.recursive, + excludes: input.excludePaths, + }); + console.log('this._watchHandle', this._watchHandle); + this._watchHandle.onEvent((event: any) => { + this.events.push( + ...event.paths.map((path: string) => ({ + path, + type: + event.type === 'add' + ? 'create' + : event.type === 'remove' + ? 'delete' + : ('update' as 'create' | 'update' | 'delete'), + })), + ); + + // avoid memory leak + this.events = this.events.slice(0, this.maxEvents - 1); + + if (timeout) { + clearTimeout(timeout); + } + + // debounce the resolution of the promise + timeout = setTimeout(() => { + this._onEventCallbacks.forEach((callback) => + callback({ + id: uuid(), + events: this.events, + }), + ); + }, 200); + }); + } + + onOutput(callback: (output: SandboxFileWatchOutput) => void): void { + this._onEventCallbacks.push(callback); + } + + async stop(): Promise { + if (this._watchHandle) { + await this._watchHandle.dispose(); + this._watchHandle = null; + } + + this._off = true; + this.events = []; + } +} diff --git a/apps/coderouter/src/provider/codesandbox/sandbox/index.ts b/apps/coderouter/src/provider/codesandbox/sandbox/index.ts new file mode 100644 index 0000000000..9a7d67cc87 --- /dev/null +++ b/apps/coderouter/src/provider/codesandbox/sandbox/index.ts @@ -0,0 +1,126 @@ +import { CodesandboxClient } from '../index'; +import { + Sandbox, + SandboxCreateInput, + SandboxCreateOutput, + SandboxGetInput, + SandboxGetOutput, + SandboxPauseInput, + SandboxPauseOutput, + SandboxResumeInput, + SandboxResumeOutput, + SandboxStopInput, + SandboxStopOutput, + SandboxUrlInput, + SandboxUrlOutput, +} from '@/provider/definition/sandbox'; +import { CodeSandbox, Sandbox as _CodesandboxSandbox } from '@codesandbox/sdk'; +import { ClientError, ClientErrorCode } from '@/provider/definition'; +import { CodesandboxSandboxFile } from './file'; +import { CodesandboxSandboxTerminal } from './terminal'; + +export class CodesandboxSandbox extends Sandbox { + public readonly file: CodesandboxSandboxFile; + public readonly terminal: CodesandboxSandboxTerminal; + + constructor(protected readonly client: CodesandboxClient) { + super(client); + this.file = new CodesandboxSandboxFile(this.client); + this.terminal = new CodesandboxSandboxTerminal(this.client); + } + + async beforeSandboxCall(): Promise { + this.client._sdk = new CodeSandbox(); + if (this.client.options.sandboxId) { + this.client._sandbox = await this.client._sdk.sandboxes.resume( + this.client.options.sandboxId, + ); + this.client._client = await this.client._sandbox.createRestSession(); + } + } + + async create(input: SandboxCreateInput): Promise { + const sandboxId = this.client.options.sandboxId; + if (!sandboxId) { + throw new ClientError( + ClientErrorCode.MissingSandboxId, + 'Sandbox ID is not set. Please provide a sandbox ID in the JWT token.', + false, + ); + } + if (!input.templateId) { + throw new ClientError( + ClientErrorCode.MissingTemplateId, + 'Template ID is not set. Please provide a template ID.', + false, + ); + } + const sdk = new CodeSandbox(); + + const newSandbox = await sdk.sandboxes.create({ + id: sandboxId, + source: 'template', + title: input.metadata['title'], + description: input.metadata['description'], + tags: input.metadata['tags']?.split(','), + }); + + return { + externalId: newSandbox.id, + }; + } + + async get(input: SandboxGetInput): Promise { + if (!this.client.options.sandboxId) { + throw new ClientError( + ClientErrorCode.MissingSandboxId, + 'Sandbox ID is not set. Please provide a sandbox ID in the JWT token.', + false, + ); + } + // not a big fan of this, however, the other way is to filter a list of sandboxes and the API + // is not great for that + const sdk = new CodeSandbox(); + const sandbox = await sdk.sandboxes.resume(this.client.options.sandboxId); + return { + id: this.client.options.sandboxId, + externalId: sandbox.id, + }; + } + + async pause(input: SandboxPauseInput): Promise { + if (!this.client.options.sandboxId) { + throw new ClientError( + ClientErrorCode.MissingSandboxId, + 'Sandbox ID is not set. Please provide a sandbox ID in the JWT token.', + false, + ); + } + const sdk = new CodeSandbox(); + await sdk.sandboxes.hibernate(this.client.options.sandboxId); + return {}; + } + + async resume(input: SandboxResumeInput): Promise { + return {}; + } + + async stop(input: SandboxStopInput): Promise { + if (!this.client.options.sandboxId) { + throw new ClientError( + ClientErrorCode.MissingSandboxId, + 'Sandbox ID is not set. Please provide a sandbox ID in the JWT token.', + false, + ); + } + const sdk = new CodeSandbox(); + await sdk.sandboxes.shutdown(this.client.options.sandboxId); + return {}; + } + + async url(input: SandboxUrlInput): Promise { + return { + url: `https://${this.client.options.sandboxId}-3000.csb.app`, + }; + } +} diff --git a/apps/coderouter/src/provider/codesandbox/sandbox/terminal/index.ts b/apps/coderouter/src/provider/codesandbox/sandbox/terminal/index.ts new file mode 100644 index 0000000000..39639af390 --- /dev/null +++ b/apps/coderouter/src/provider/codesandbox/sandbox/terminal/index.ts @@ -0,0 +1,276 @@ +import { ClientError, ClientErrorCode } from '@/provider/definition'; +import { CodesandboxClient } from '../..'; +import { + SandboxTerminal, + SandboxTerminalCommandInput, + SandboxTerminalCommandOutput, + SandboxTerminalCreateInput, + SandboxTerminalCreateOutput, + SandboxTerminalOpenInput, + SandboxTerminalOpenOutput, + SandboxTerminalWriteInput, + SandboxTerminalWriteOutput, + SandboxTerminalRunInput, + SandboxTerminalRunOutput, + SandboxTerminalKillInput, + SandboxTerminalKillOutput, +} from '../../../definition/sandbox/terminal'; +import { Terminal as CodesandboxTerminal } from '@codesandbox/sdk'; +import { v4 as uuid } from 'uuid'; + +// sandboxId -> terminalId -> TerminalWatcher +const terminalWatchers: Map> = new Map(); + +export class CodesandboxSandboxTerminal extends SandboxTerminal { + constructor(client: CodesandboxClient) { + super(client); + } + + async command(input: SandboxTerminalCommandInput): Promise { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ); + } + + try { + const client = await this.client._sandbox.connect(); + const result = await client.commands.run(input.command); + return { + output: result, + is_error: false, + stdout: undefined, + stderr: undefined, + exit_code: 0, + }; + } catch (error) { + throw new ClientError( + ClientErrorCode.ImplementationError, + `Failed to execute command: ${error}`, + false, + ); + } + } + + async create(input: SandboxTerminalCreateInput): Promise { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ); + } + + const sandboxId = this.client._sandbox.id; + if (!sandboxId) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not running. Call start() or resume() first.', + false, + ); + } + + // Create a new terminal watcher for this terminal ID + if (!terminalWatchers.has(sandboxId)) { + terminalWatchers.set(sandboxId, new Map()); + } + if (!terminalWatchers.get(sandboxId)!.has(input.terminalId)) { + terminalWatchers + .get(sandboxId)! + .set(input.terminalId, new TerminalWatcher(this.client, input.terminalId)); + } + + terminalWatchers.get(sandboxId)!.get(input.terminalId)!.start(); + + return { + terminalId: input.terminalId, + name: input.name, + }; + } + + async open( + input: SandboxTerminalOpenInput, + onOutput: (output: SandboxTerminalOpenOutput) => void, + ) { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ); + } + + const watcher = terminalWatchers.get(this.client._sandbox.id)?.get(input.terminalId); + if (!watcher) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Terminal not found. Call create() first.', + false, + ); + } + + // Start the terminal watcher if it's not already running + if (watcher.off) { + await watcher.start(); + } + + const unsubscribe = watcher.onOutput((output) => onOutput(output)); + + return { + close: () => { + unsubscribe(); + }, + }; + } + + async write(input: SandboxTerminalWriteInput): Promise { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ); + } + + const watcher = terminalWatchers.get(this.client._sandbox.id)?.get(input.terminalId); + if (!watcher) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Terminal not found. Call create() first.', + false, + ); + } + + // Write input to the terminal + await watcher.writeInput(input.input); + + return {}; + } + + async run(input: SandboxTerminalRunInput): Promise { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ); + } + + const watcher = terminalWatchers.get(this.client._sandbox.id)?.get(input.terminalId); + if (!watcher) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Terminal not found. Call create() first.', + false, + ); + } + + // Execute the command and get output + await watcher.executeCommand(input.input); + + return {}; + } + + async kill(input: SandboxTerminalKillInput): Promise { + if (!this.client._sandbox) { + return { + output: 'Sandbox is not instantiated. Call start() or resume() first.', + }; + } + + const watcher = terminalWatchers.get(this.client._sandbox.id)?.get(input.terminalId); + if (watcher) { + await watcher.stop(); + terminalWatchers.get(this.client._sandbox.id)?.delete(input.terminalId); + } + + return { + output: 'Terminal killed', + }; + } +} + +class TerminalWatcher { + protected readonly watchTimeout = 0; + + protected _off: boolean = true; + protected _terminalHandle: CodesandboxTerminal | null = null; + protected _onOutputCallbacks: Array<(output: SandboxTerminalOpenOutput) => void> = []; + + constructor( + protected readonly client: CodesandboxClient, + protected readonly terminalId: string, + ) {} + + get off(): boolean { + return this._off; + } + + async start(): Promise { + if (!this.client._sandbox) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Sandbox is not instantiated. Call start() or resume() first.', + false, + ); + } + + if (!this._off) { + return; + } + + this._off = false; + + const client = await this.client._sandbox.connect(); + + // Create PTY terminal with real-time data callback + this._terminalHandle = await client.terminals.create(); + } + + onOutput(callback: (output: SandboxTerminalOpenOutput) => void): () => void { + const disposable = this._terminalHandle?.onOutput((output) => + callback({ id: uuid(), output }), + ); + return () => { + disposable?.dispose(); + }; + } + + async writeInput(input: string): Promise { + if (!this._terminalHandle) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Terminal not initialized. Call start() first.', + false, + ); + } + + this._terminalHandle.write(input); + } + + async executeCommand(command: string): Promise { + if (!this._terminalHandle) { + throw new ClientError( + ClientErrorCode.ImplementationError, + 'Terminal not initialized. Call start() first.', + false, + ); + } + + this._terminalHandle.run(command); + } + + async stop(): Promise { + this._off = true; + + // Close the PTY terminal + if (this._terminalHandle) { + await this._terminalHandle.kill(); + this._terminalHandle = null; + } + + this._onOutputCallbacks = []; + } +} diff --git a/apps/coderouter/src/provider/e2b/sandbox/index.ts b/apps/coderouter/src/provider/e2b/sandbox/index.ts index e62a412c80..1ad510150e 100644 --- a/apps/coderouter/src/provider/e2b/sandbox/index.ts +++ b/apps/coderouter/src/provider/e2b/sandbox/index.ts @@ -23,7 +23,7 @@ export class E2BSandbox extends Sandbox { public readonly file: E2BSandboxFile; public readonly terminal: E2BSandboxTerminal; - private _sandboxTimeoutMs: number = 1000 * 60 * 3; + private _sandboxTimeoutMs: number = 1000 * 60 * 10; constructor(protected readonly client: E2BClient) { super(client); diff --git a/apps/docker-compose.yaml b/apps/docker-compose.yaml index a6b24b06b1..7109fcf7ee 100644 --- a/apps/docker-compose.yaml +++ b/apps/docker-compose.yaml @@ -4,6 +4,8 @@ name: onlook-web services: coderouter: container_name: coderouter + environment: + - DEBUG=* build: context: ./coderouter dockerfile: Dockerfile.dev @@ -11,6 +13,7 @@ services: - '4444:4444' volumes: - ./coderouter:/app + - coderouter_node_modules:/app/node_modules command: [/usr/local/bin/bun/bin/bun, dev] nginx: container_name: nginx @@ -23,3 +26,5 @@ services: - ./nginx/ssl/onlook-internal.crt:/etc/nginx/ssl/onlook-internal.crt:ro - ./nginx/ssl/onlook-internal.key:/etc/nginx/ssl/onlook-internal.key:ro command: [nginx, '-g', 'daemon off;'] +volumes: + coderouter_node_modules: diff --git a/apps/web/client/src/components/store/editor/sandbox/session.ts b/apps/web/client/src/components/store/editor/sandbox/session.ts index 5c5973d9e8..d7829de711 100644 --- a/apps/web/client/src/components/store/editor/sandbox/session.ts +++ b/apps/web/client/src/components/store/editor/sandbox/session.ts @@ -103,10 +103,7 @@ export class SessionManager { // Initialize the sessions after creation try { - await Promise.all([ - task.initTask(), - terminal.initTerminal() - ]); + await Promise.all([task.initTask(), terminal.initTerminal()]); } catch (error) { console.error('Failed to initialize terminal sessions:', error); } diff --git a/bun.lock b/bun.lock index 3b56d7f91d..4738083b58 100644 --- a/bun.lock +++ b/bun.lock @@ -23,6 +23,7 @@ "name": "coderouter", "version": "0.0.1", "dependencies": { + "@codesandbox/sdk": "^1.1.6", "@e2b/code-interpreter": "2.0.0", "@hono/zod-openapi": "^1.1.0", "@hono/zod-validator": "^0.2.1", diff --git a/packages/code-provider/src/providers/coderouter/index.ts b/packages/code-provider/src/providers/coderouter/index.ts index 92153e4efb..fc8d875203 100644 --- a/packages/code-provider/src/providers/coderouter/index.ts +++ b/packages/code-provider/src/providers/coderouter/index.ts @@ -362,6 +362,7 @@ export class CoderouterProvider extends Provider { } async pauseProject(input: PauseProjectInput): Promise { + await this.api.coderouterApiSandboxPausePost({}, this.requestInitOverrides()); return {}; }