Skip to content

Commit 6139f12

Browse files
author
Thomas Potaire
committed
implementing terminal
1 parent 6b6b3fa commit 6139f12

34 files changed

+2293
-75
lines changed

apps/coderouter/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"jsonwebtoken": "^9.0.2",
2626
"mysql2": "^3.9.7",
2727
"pg": "^8.11.5",
28+
"uuid": "^12.0.0",
2829
"zod": "^4.0.17"
2930
},
3031
"devDependencies": {

apps/coderouter/src/api/sandbox/terminal/command/index.test.ts

Whitespace-only changes.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import {
2+
SandboxTerminalCommandInput,
3+
SandboxTerminalCommandOutput,
4+
} from '@/provider/definition/sandbox/terminal';
5+
import { LocalHono } from '@/server';
6+
import { JwtAuthResponses } from '@/util/auth';
7+
import { createRoute, z } from '@hono/zod-openapi';
8+
import { env } from 'bun';
9+
10+
const BodySchema: z.ZodType<SandboxTerminalCommandInput> = z.object({
11+
command: z.string().openapi({
12+
description: 'The command to run.',
13+
example: 'ls -la',
14+
}),
15+
});
16+
17+
const ResponseSchema: z.ZodType<SandboxTerminalCommandOutput> = z.object({
18+
is_error: z.boolean().openapi({
19+
description: 'Whether the command was successful.',
20+
example: true,
21+
}),
22+
output: z.string().openapi({
23+
description: 'The output of the command.',
24+
example: 'ls -la',
25+
}),
26+
stdout: z.string().openapi({
27+
description: 'The stdout of the command.',
28+
example: 'ls -la',
29+
}),
30+
stderr: z.string().openapi({
31+
description: 'The stderr of the command.',
32+
example: 'ls -la',
33+
}),
34+
exit_code: z.number().openapi({
35+
description: 'The exit code of the command.',
36+
example: 0,
37+
}),
38+
});
39+
40+
const route = createRoute({
41+
method: 'post',
42+
path: env.URL_PATH_PREFIX + '/api/sandbox/terminal/command',
43+
security: [{ jwt: [] }],
44+
request: {
45+
body: {
46+
content: {
47+
'application/json': {
48+
schema: BodySchema,
49+
},
50+
},
51+
},
52+
},
53+
responses: {
54+
200: {
55+
content: {
56+
'application/json': {
57+
schema: ResponseSchema,
58+
},
59+
},
60+
description: 'Run a command in the sandbox.',
61+
},
62+
...JwtAuthResponses,
63+
},
64+
});
65+
66+
export function api_sandbox_terminal_command(app: LocalHono) {
67+
app.openapi(route, async (c) => {
68+
const body = await c.req.valid('json');
69+
const result = await c.get('client').sandbox.terminal.command(body);
70+
return c.json(result, 200);
71+
});
72+
}

apps/coderouter/src/api/sandbox/terminal/create/index.test.ts

Whitespace-only changes.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import {
2+
SandboxTerminalCreateInput,
3+
SandboxTerminalCreateOutput,
4+
} from '@/provider/definition/sandbox/terminal';
5+
import { LocalHono } from '@/server';
6+
import { JwtAuthResponses } from '@/util/auth';
7+
import { createRoute, z } from '@hono/zod-openapi';
8+
import { env } from 'bun';
9+
10+
const BodySchema: z.ZodType<SandboxTerminalCreateInput> = z.object({
11+
terminalId: z.string().openapi({
12+
description: 'The ID of the terminal.',
13+
example: '00000000-0000-0000-0000-000000000000 or any unique string',
14+
}),
15+
name: z.string().openapi({
16+
description: 'The name of the terminal.',
17+
example: 'My Terminal',
18+
}),
19+
});
20+
21+
const ResponseSchema: z.ZodType<SandboxTerminalCreateOutput> = z.object({
22+
terminalId: z.string().openapi({
23+
description: 'The ID of the terminal.',
24+
example: '00000000-0000-0000-0000-000000000000 or any unique string',
25+
}),
26+
name: z.string().openapi({
27+
description: 'The name of the terminal.',
28+
example: 'My Terminal',
29+
}),
30+
});
31+
32+
const route = createRoute({
33+
method: 'post',
34+
path: env.URL_PATH_PREFIX + '/api/sandbox/terminal/create',
35+
security: [{ jwt: [] }],
36+
request: {
37+
body: {
38+
content: {
39+
'application/json': {
40+
schema: BodySchema,
41+
},
42+
},
43+
},
44+
},
45+
responses: {
46+
200: {
47+
content: {
48+
'application/json': {
49+
schema: ResponseSchema,
50+
},
51+
},
52+
description: 'Create a terminal in the sandbox.',
53+
},
54+
...JwtAuthResponses,
55+
},
56+
});
57+
58+
export function api_sandbox_terminal_create(app: LocalHono) {
59+
app.openapi(route, async (c) => {
60+
const body = await c.req.valid('json');
61+
const result = await c.get('client').sandbox.terminal.create(body);
62+
return c.json(result, 200);
63+
});
64+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import {
2+
SandboxTerminalKillInput,
3+
SandboxTerminalKillOutput,
4+
} from '@/provider/definition/sandbox/terminal';
5+
import { LocalHono } from '@/server';
6+
import { JwtAuthResponses } from '@/util/auth';
7+
import { createRoute, z } from '@hono/zod-openapi';
8+
import { env } from 'bun';
9+
10+
const BodySchema: z.ZodType<SandboxTerminalKillInput> = z.object({
11+
terminalId: z.string().openapi({
12+
description: 'The ID of the terminal.',
13+
example: '00000000-0000-0000-0000-000000000000 or any unique string',
14+
}),
15+
});
16+
17+
const ResponseSchema: z.ZodType<SandboxTerminalKillOutput> = z.object({
18+
output: z.string().openapi({
19+
description: 'The output of the kill command.',
20+
example: 'Terminal killed',
21+
}),
22+
});
23+
24+
const route = createRoute({
25+
method: 'post',
26+
path: env.URL_PATH_PREFIX + '/api/sandbox/terminal/kill',
27+
security: [{ jwt: [] }],
28+
request: {
29+
body: {
30+
content: {
31+
'application/json': {
32+
schema: BodySchema,
33+
},
34+
},
35+
},
36+
},
37+
responses: {
38+
200: {
39+
content: {
40+
'application/json': {
41+
schema: ResponseSchema,
42+
},
43+
},
44+
description: 'Kill a terminal in the sandbox.',
45+
},
46+
...JwtAuthResponses,
47+
},
48+
});
49+
50+
export function api_sandbox_terminal_kill(app: LocalHono) {
51+
app.openapi(route, async (c) => {
52+
const body = await c.req.valid('json');
53+
const result = await c.get('client').sandbox.terminal.kill(body);
54+
return c.json(result, 200);
55+
});
56+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// import {
2+
// SandboxTerminalOpenInput,
3+
// SandboxTerminalOpenOutput,
4+
// } from '@/provider/definition/sandbox/terminal';
5+
import { SandboxTerminalOpenOutput } from '@/provider/definition/sandbox/terminal';
6+
import { LocalHono } from '@/server';
7+
import { JwtAuthResponses } from '@/util/auth';
8+
import { createRoute, z } from '@hono/zod-openapi';
9+
import { env, sleep } from 'bun';
10+
import { streamSSE } from 'hono/streaming';
11+
import { v4 as uuid } from 'uuid';
12+
13+
const ParamsSchema = z.object({
14+
terminalId: z.string().openapi({
15+
description: 'The ID of the terminal.',
16+
example: '00000000-0000-0000-0000-000000000000 or any unique string',
17+
}),
18+
});
19+
20+
const route = createRoute({
21+
method: 'get',
22+
path: env.URL_PATH_PREFIX + '/api/sandbox/terminal/open',
23+
security: [{ jwt: [] }],
24+
request: {
25+
query: ParamsSchema,
26+
},
27+
responses: {
28+
200: {
29+
content: {
30+
'text/event-stream': {
31+
schema: z.string().openapi({
32+
description:
33+
'A stream of server-sent events (not JSON array, continuous text).',
34+
}),
35+
},
36+
},
37+
description: 'Return the output of a terminal in the sandbox. Design for long-polling',
38+
},
39+
...JwtAuthResponses,
40+
},
41+
});
42+
43+
export function api_sandbox_terminal_open(app: LocalHono) {
44+
app.openapi(route, async (c) => {
45+
const query = await c.req.valid('query');
46+
return streamSSE(c, async (stream) => {
47+
// Send a "connected" message immediately
48+
await stream.writeSSE({
49+
id: uuid(),
50+
event: 'status',
51+
data: 'Connected to endpoint.',
52+
});
53+
54+
const onOutput = (res: SandboxTerminalOpenOutput) => {
55+
console.log(res.output);
56+
stream.writeSSE({
57+
id: res.id,
58+
event: 'message',
59+
data: JSON.stringify({ output: res.output }),
60+
});
61+
};
62+
63+
const { close } = await c.get('client').sandbox.terminal.open(query, onOutput);
64+
65+
let open = true;
66+
stream.onAbort(() => {
67+
close();
68+
open = false;
69+
});
70+
71+
while (open) {
72+
await stream.writeSSE({
73+
id: uuid(),
74+
event: 'status',
75+
data: 'Endpoint is still open.',
76+
});
77+
await sleep(5000);
78+
}
79+
});
80+
});
81+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import {
2+
SandboxTerminalRunInput,
3+
SandboxTerminalRunOutput,
4+
} from '@/provider/definition/sandbox/terminal';
5+
import { LocalHono } from '@/server';
6+
import { JwtAuthResponses } from '@/util/auth';
7+
import { createRoute, z } from '@hono/zod-openapi';
8+
import { env } from 'bun';
9+
10+
const BodySchema: z.ZodType<SandboxTerminalRunInput> = z.object({
11+
terminalId: z.string().openapi({
12+
description: 'The ID of the terminal.',
13+
example: '00000000-0000-0000-0000-000000000000 or any unique string',
14+
}),
15+
input: z.string().openapi({
16+
description: 'The name of the terminal.',
17+
example: 'My Terminal',
18+
}),
19+
});
20+
21+
const ResponseSchema: z.ZodType<SandboxTerminalRunOutput> = z.object({
22+
output: z.string().openapi({
23+
description:
24+
'The output of the run command. The output includes lines before and after the command execution.',
25+
example: 'My Terminal',
26+
}),
27+
});
28+
29+
const route = createRoute({
30+
method: 'post',
31+
path: env.URL_PATH_PREFIX + '/api/sandbox/terminal/run',
32+
security: [{ jwt: [] }],
33+
request: {
34+
body: {
35+
content: {
36+
'application/json': {
37+
schema: BodySchema,
38+
},
39+
},
40+
},
41+
},
42+
responses: {
43+
200: {
44+
content: {
45+
'application/json': {
46+
schema: ResponseSchema,
47+
},
48+
},
49+
description: 'Run a terminal in the sandbox.',
50+
},
51+
...JwtAuthResponses,
52+
},
53+
});
54+
55+
export function api_sandbox_terminal_run(app: LocalHono) {
56+
app.openapi(route, async (c) => {
57+
const body = await c.req.valid('json');
58+
const result = await c.get('client').sandbox.terminal.run(body);
59+
return c.json(result, 200);
60+
});
61+
}

0 commit comments

Comments
 (0)