Skip to content

Commit 4ae9197

Browse files
committed
bunch of fixes in apps/web and apps/bot
1 parent 9c14f97 commit 4ae9197

27 files changed

+384
-719
lines changed

apps/bot/Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ FROM node:22-slim AS runtime
2222
WORKDIR /app
2323
ENV NODE_ENV="production"
2424
COPY --from=build /runtime ./
25+
COPY --from=build /repo/apps/bot/lib ./lib
2526
CMD ["node", "./lib/server.js"]

apps/bot/app.yml

Lines changed: 0 additions & 137 deletions
This file was deleted.

apps/bot/fly.toml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@ primary_region = 'sjc'
99
[build]
1010
dockerfile = "Dockerfile"
1111

12-
[processes]
13-
app = "node ./lib/server.js"
14-
1512
[deploy]
1613
strategy = 'bluegreen'
1714

apps/bot/package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@
1010
},
1111
"dependencies": {
1212
"@t3-oss/env-core": "^0.13.8",
13-
"probot": "^13.4.5",
13+
"probot": "^13.4.7",
1414
"zod": "^4.1.13"
1515
},
1616
"devDependencies": {
17-
"@types/node": "^20.0.0",
18-
"nock": "^14.0.5",
19-
"smee-client": "^2.0.0",
20-
"typescript": "^5.8.3",
21-
"vitest": "^2.0.0"
17+
"@types/node": "^20.19.25",
18+
"nock": "^14.0.10",
19+
"smee-client": "^2.0.4",
20+
"typescript": "^5.9.3",
21+
"vitest": "^2.1.9"
2222
},
2323
"overrides": {
2424
"@types/pg": "8.15.1"

apps/bot/src/devin/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export * from "./list";
2+
export * from "./send";
3+
export * from "./shared";
4+
export * from "./terminate";

apps/bot/src/devin/list.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// https://docs.devin.ai/api-reference/sessions/list-sessions.
2+
import { DEVIN_API_BASE_URL, fetchFromDevin } from "./shared";
3+
4+
const DEFAULT_PAGE_SIZE = 100;
5+
const MAX_PAGES = 50;
6+
7+
export interface DevinSession {
8+
session_id: string;
9+
status: string;
10+
created_at: string;
11+
updated_at: string;
12+
title?: string;
13+
pull_request: {
14+
url: string;
15+
} | null;
16+
}
17+
18+
interface ListSessionsResponse {
19+
sessions: DevinSession[];
20+
}
21+
22+
interface ListSessionsOptions {
23+
limit?: number;
24+
offset?: number;
25+
status?: string;
26+
}
27+
28+
export async function listDevinSessions(
29+
options: ListSessionsOptions = {},
30+
): Promise<ListSessionsResponse> {
31+
const url = new URL(`${DEVIN_API_BASE_URL}/sessions`);
32+
const limit = options.limit ?? DEFAULT_PAGE_SIZE;
33+
const offset = options.offset ?? 0;
34+
35+
url.searchParams.set("limit", limit.toString());
36+
url.searchParams.set("offset", offset.toString());
37+
38+
if (options.status) {
39+
url.searchParams.set("status", options.status);
40+
}
41+
42+
const response = await fetchFromDevin(url.toString(), {
43+
method: "GET",
44+
});
45+
46+
return (await response.json()) as ListSessionsResponse;
47+
}
48+
49+
export async function findRunningSessionForPR(
50+
prUrl: string,
51+
): Promise<DevinSession | null> {
52+
let offset = 0;
53+
const limit = DEFAULT_PAGE_SIZE;
54+
55+
for (let i = 0; i < MAX_PAGES; i++) {
56+
const { sessions } = await listDevinSessions({
57+
limit,
58+
offset,
59+
status: "running",
60+
});
61+
62+
if (sessions.length === 0) {
63+
break;
64+
}
65+
66+
const match = sessions.find(
67+
(session) => session.pull_request?.url === prUrl,
68+
);
69+
if (match) {
70+
return match;
71+
}
72+
73+
if (sessions.length < limit) {
74+
break;
75+
}
76+
77+
offset += limit;
78+
}
79+
80+
return null;
81+
}

apps/bot/src/devin/send.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// https://docs.devin.ai/api-reference/sessions/send-a-message-to-an-existing-devin-session
2+
import { DEVIN_API_BASE_URL, fetchFromDevin } from "./shared";
3+
4+
export async function sendMessageToDevinSession(
5+
sessionId: string,
6+
message: string,
7+
): Promise<void> {
8+
await fetchFromDevin(`${DEVIN_API_BASE_URL}/sessions/${sessionId}/message`, {
9+
method: "POST",
10+
headers: {
11+
"Content-Type": "application/json",
12+
},
13+
body: JSON.stringify({ message }),
14+
});
15+
}

apps/bot/src/devin/shared.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { env } from "../env";
2+
3+
export const DEVIN_API_BASE_URL = "https://api.devin.ai/v1";
4+
5+
export async function fetchFromDevin(
6+
input: string | URL,
7+
init?: RequestInit,
8+
): Promise<Response> {
9+
const response = await fetch(input, {
10+
...init,
11+
headers: {
12+
Authorization: `Bearer ${env.DEVIN_API_KEY}`,
13+
...(init?.headers ?? {}),
14+
},
15+
});
16+
17+
if (!response.ok) {
18+
const body = await response.text();
19+
throw new Error(
20+
`Devin API request failed: ${response.status} ${response.statusText} - ${body}`,
21+
);
22+
}
23+
24+
return response;
25+
}

apps/bot/src/devin/terminate.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// https://docs.devin.ai/api-reference/sessions/terminate-a-session.
2+
import { DEVIN_API_BASE_URL, fetchFromDevin } from "./shared";
3+
4+
export async function terminateDevinSession(sessionId: string): Promise<void> {
5+
await fetchFromDevin(`${DEVIN_API_BASE_URL}/sessions/${sessionId}`, {
6+
method: "DELETE",
7+
});
8+
}

apps/bot/src/env.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const env = createEnv({
88
GITHUB_BOT_WEBHOOK_SECRET: z.string().min(1),
99
GITHUB_BOT_CLIENT_ID: z.string().min(1).optional(),
1010
GITHUB_BOT_CLIENT_SECRET: z.string().min(1).optional(),
11+
DEVIN_API_KEY: z.string().min(1).optional(),
1112
},
1213
runtimeEnv: process.env,
1314
emptyStringAsUndefined: true,

0 commit comments

Comments
 (0)