-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[Work-in-progress] Code Provider API with E2B #2711
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 3 commits
a7e1d22
e016242
6b6b3fa
6139f12
31467ff
565422c
6a37bea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. | ||
CODEROUTER_API_KEY="" | ||
|
||
E2B_API_KEY="Get your API key at https://e2b.dev" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
node_modules | ||
.env |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"$schema": "https://biomejs.dev/schemas/2.1.4/schema.json", | ||
"formatter": { "enabled": true, "formatWithErrors": true }, | ||
"linter": { "enabled": true } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[install] | ||
exact = false |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
coverage: | ||
status: | ||
project: | ||
default: | ||
target: 60% | ||
threshold: 5% |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -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, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+1
to
+15
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. drizzle-kit config uses the wrong credential key; use drizzle-kit expects dbCredentials.url. Using connectionString will break migrations. Adding typing guards against config drift. import "dotenv/config";
+import type { Config } from "drizzle-kit";
-export default {
+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,
+ url: process.env.DATABASE_URL!,
},
-};
+} satisfies Config; 📝 Committable suggestion
Suggested change
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -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" | ||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainVerify zod v4 compatibility with @hono/zod-openapi v1.1.0 There have been breaking changes in Zod v4; many libraries still target Zod v3. Confirm compatibility to avoid subtle type/runtime issues. If incompatible, pin Zod to ^3.23.x or upgrade @hono/zod-openapi accordingly. 🌐 Web query:
💡 Result: Short answer: It still expects Zod v3 — @hono/zod-openapi v1.1.0 does not (yet) support Zod v4. Evidence:
If you want, I can:
Citations:
Fix Zod compatibility and pin Bun engine Your current setup uses Zod v4 alongside @hono/zod-openapi v1.1.0, which still expects Zod v3. To prevent runtime/type errors, you must downgrade Zod to the peer range declared by @hono/zod-openapi (v3.x) or upgrade to a middleware version that supports Zod v4 once available. Also, declaring an engines field for Bun will lock the runtime version for consistent environments. • apps/coderouter/package.json: "dependencies": {
- "zod": "^4.0.17",
+ "zod": "^3.23.0",
// …other deps…
},
"scripts": {
"dev": "bun run --hot src/server.ts",
"start": "bun run src/server.ts",
"db:migrate": "bun run scripts/migrate.ts"
},
+ "engines": {
+ "bun": ">=1.1.0"
+ } References:
📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||
"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" | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"$schema": "https://docs.renovatebot.com/renovate-schema.json", | ||
"extends": ["config:recommended"], | ||
"automerge": false | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<!doctype html> | ||
<html> | ||
<head> | ||
<meta charset="utf-8" /> | ||
<title>Project Docs</title> | ||
</head> | ||
<body> | ||
<h1>Project Documentation</h1> | ||
<ul> | ||
<li><a href="./api/">API (Swagger UI)</a></li> | ||
<li><a href="./typedoc/">TypeDoc</a></li> | ||
</ul> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -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<AuthSignInput> = 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.", | ||||||||||||||
}, | ||||||||||||||
|
description: | |
"Create a new sandbox. If the sandbox already exists, the system will resume the sandbox.", | |
}, | |
description: | |
"Issue a JWT token for API authentication.", | |
}, |
🤖 Prompt for AI Agents
In apps/coderouter/src/api/auth/sign/index.ts around lines 50 to 52, the OpenAPI
response description wrongly mentions creating/resuming a sandbox; replace that
description with an accurate one stating that the endpoint issues/returns a JWT
access token (or authentication token) for the authenticated user, and ensure
the wording matches any response schema (e.g., "Returns a JWT access token for
authentication") so docs reflect the endpoint's true behavior.
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,18 @@ | ||||||||||||||
import { describe, expect, it } from 'bun:test'; | ||||||||||||||
import { Hono } from 'hono'; | ||||||||||||||
import { api_sandbox_create } from './index'; | ||||||||||||||
Comment on lines
+2
to
+3
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test uses Hono without OpenAPI support; The route implementation registers via Apply this diff to convert the test to a working harness with stubs and correct assertions: -import { describe, expect, it } from 'bun:test';
-import { Hono } from 'hono';
-import { api_sandbox_create } from './index';
+import { describe, expect, it } from 'bun:test';
+import { OpenAPIHono } from '@hono/zod-openapi';
+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({});
- });
+ it('POST /coderouter/api/sandbox/create returns id', async () => {
+ const app = new OpenAPIHono();
+ // Provide required context for the handler
+ app.use('*', async (c, next) => {
+ c.set('client', {
+ sandbox: {
+ get: async () => ({}),
+ resume: async () => ({}),
+ create: async () => ({}),
+ },
+ });
+ c.set('auth', { sandboxId: 'test-sandbox' });
+ await next();
+ });
+ api_sandbox_create(app);
+
+ const response = await app.request('/coderouter/api/sandbox/create', {
+ method: 'POST',
+ headers: { 'content-type': 'application/json' },
+ body: JSON.stringify({}),
+ });
+
+ expect(response.status).toBe(200);
+ const body = await response.json();
+ expect(body).toEqual({ id: 'test-sandbox' });
+ });
}); Also applies to: 7-9 🤖 Prompt for AI Agents
|
||||||||||||||
|
||||||||||||||
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', | ||||||||||||||
}); | ||||||||||||||
Comment on lines
+10
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
🤖 Prompt for AI Agents
|
||||||||||||||
|
||||||||||||||
expect(response.status).toBe(201); | ||||||||||||||
const body = await response.json(); | ||||||||||||||
expect(body).toEqual({}); | ||||||||||||||
Comment on lines
+14
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainStatus code and response shape mismatch with implementation Implementation returns 200 with To confirm the current response contract across the codebase, run: 🏁 Script executed: #!/bin/bash
# Inspect sandbox create handler for status and body shape
rg -n -C2 --type=ts "api_sandbox_create|sandboxId|c\.json\s*\(" apps/coderouter/src Length of output: 44302 Update sandbox create test to match implementation The test currently expects a 201 status and an empty body, but the handler returns 200 with an • Expect a 200 status: - expect(response.status).toBe(201);
+ expect(response.status).toBe(200); • Expect the response body to contain an - expect(body).toEqual({});
+ expect(body).toEqual({ id: expect.any(String) }); Make sure the test supplies a mock 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||
}); | ||||||||||||||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Fix nested list indentation and a split bullet to satisfy markdownlint (MD007) and readability
The nested checklist items are indented with 4 spaces; your repo’s lint expects 2. Also, the “Detect and use Components” bullet is split across two lines, breaking the markdown structure.
Apply the following diffs:
Also applies to: 77-85, 87-90, 93-97
🧰 Tools
🪛 LanguageTool
[grammar] ~72-~72: There might be a mistake here.
Context: ...conds - [x] Start from text or image - [ ] Use prebuilt templates - [ ] Imp...
(QB_NEW_EN)
[grammar] ~73-~73: There might be a mistake here.
Context: ...r image - [ ] Use prebuilt templates - [ ] Import from Figma - [ ] Start fr...
(QB_NEW_EN)
[grammar] ~74-~74: There might be a mistake here.
Context: ...lt templates - [ ] Import from Figma - [ ] Start from GitHub repo - [x] Visuall...
(QB_NEW_EN)
[grammar] ~75-~75: There might be a mistake here.
Context: ...m Figma - [ ] Start from GitHub repo - [x] Visually edit your app - [x] Use...
(QB_NEW_EN)
🪛 markdownlint-cli2 (0.17.2)
72-72: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
73-73: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
74-74: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
75-75: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
🤖 Prompt for AI Agents