mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 08:03:15 +00:00
feat: trigger runs from GH issues (#324)
* feat: trigger runs from GH issues * cr * working * cr * done * cr * cr * cr * cr * diff label for dev vs prod * cr * cr * cr
This commit is contained in:
parent
93983d3c0a
commit
ba0690caa0
27 changed files with 697 additions and 101 deletions
|
|
@ -29,9 +29,13 @@
|
||||||
"@langchain/langgraph": "^0.3.3",
|
"@langchain/langgraph": "^0.3.3",
|
||||||
"@langchain/langgraph-sdk": "^0.0.85",
|
"@langchain/langgraph-sdk": "^0.0.85",
|
||||||
"@langchain/openai": "^0.5.10",
|
"@langchain/openai": "^0.5.10",
|
||||||
|
"@octokit/app": "^16.0.1",
|
||||||
|
"@octokit/core": "^7.0.2",
|
||||||
"@octokit/rest": "^22.0.0",
|
"@octokit/rest": "^22.0.0",
|
||||||
|
"@octokit/webhooks": "^14.0.2",
|
||||||
"@open-swe/shared": "*",
|
"@open-swe/shared": "*",
|
||||||
"diff": "^8.0.1",
|
"diff": "^8.0.1",
|
||||||
|
"hono": "^4.8.3",
|
||||||
"langchain": "^0.3.26",
|
"langchain": "^0.3.26",
|
||||||
"langsmith": "^0.3.29",
|
"langsmith": "^0.3.29",
|
||||||
"uuid": "^11.0.5",
|
"uuid": "^11.0.5",
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,12 @@
|
||||||
import { spawn } from "child_process";
|
import { spawn } from "child_process";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
|
|
||||||
|
const REQUIRED_ENV = {
|
||||||
|
GITHUB_APP_ID: "test",
|
||||||
|
GITHUB_APP_PRIVATE_KEY: "test",
|
||||||
|
GITHUB_WEBHOOK_SECRET: "test",
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the development server starts successfully.
|
* Checks if the development server starts successfully.
|
||||||
* This script starts the dev server and monitors the output for 30 seconds
|
* This script starts the dev server and monitors the output for 30 seconds
|
||||||
|
|
@ -18,6 +24,7 @@ function checkDevServer(): Promise<void> {
|
||||||
cwd: targetCwd,
|
cwd: targetCwd,
|
||||||
shell: true,
|
shell: true,
|
||||||
stdio: "pipe",
|
stdio: "pipe",
|
||||||
|
env: REQUIRED_ENV,
|
||||||
});
|
});
|
||||||
|
|
||||||
let errorDetected = false;
|
let errorDetected = false;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ import { createLangGraphClient } from "../../../utils/langgraph-client.js";
|
||||||
import {
|
import {
|
||||||
GITHUB_INSTALLATION_TOKEN_COOKIE,
|
GITHUB_INSTALLATION_TOKEN_COOKIE,
|
||||||
GITHUB_TOKEN_COOKIE,
|
GITHUB_TOKEN_COOKIE,
|
||||||
|
GITHUB_USER_ID_HEADER,
|
||||||
|
GITHUB_USER_LOGIN_HEADER,
|
||||||
} from "@open-swe/shared/constants";
|
} from "@open-swe/shared/constants";
|
||||||
import {
|
import {
|
||||||
BaseMessage,
|
BaseMessage,
|
||||||
|
|
@ -221,6 +223,10 @@ export async function classifyMessage(
|
||||||
[GITHUB_TOKEN_COOKIE]: config.configurable?.[GITHUB_TOKEN_COOKIE] ?? "",
|
[GITHUB_TOKEN_COOKIE]: config.configurable?.[GITHUB_TOKEN_COOKIE] ?? "",
|
||||||
[GITHUB_INSTALLATION_TOKEN_COOKIE]:
|
[GITHUB_INSTALLATION_TOKEN_COOKIE]:
|
||||||
config.configurable?.[GITHUB_INSTALLATION_TOKEN_COOKIE] ?? "",
|
config.configurable?.[GITHUB_INSTALLATION_TOKEN_COOKIE] ?? "",
|
||||||
|
[GITHUB_USER_ID_HEADER]:
|
||||||
|
config.configurable?.[GITHUB_USER_ID_HEADER] ?? "",
|
||||||
|
[GITHUB_USER_LOGIN_HEADER]:
|
||||||
|
config.configurable?.[GITHUB_USER_LOGIN_HEADER] ?? "",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ import { createIssueTitleAndBodyFromMessages } from "../utils/generate-issue-fie
|
||||||
import {
|
import {
|
||||||
GITHUB_INSTALLATION_TOKEN_COOKIE,
|
GITHUB_INSTALLATION_TOKEN_COOKIE,
|
||||||
GITHUB_TOKEN_COOKIE,
|
GITHUB_TOKEN_COOKIE,
|
||||||
|
GITHUB_USER_ID_HEADER,
|
||||||
|
GITHUB_USER_LOGIN_HEADER,
|
||||||
} from "@open-swe/shared/constants";
|
} from "@open-swe/shared/constants";
|
||||||
import { createLangGraphClient } from "../../../utils/langgraph-client.js";
|
import { createLangGraphClient } from "../../../utils/langgraph-client.js";
|
||||||
import { createIssue } from "../../../utils/github/api.js";
|
import { createIssue } from "../../../utils/github/api.js";
|
||||||
|
|
@ -75,6 +77,10 @@ ${ISSUE_CONTENT_CLOSE_TAG}`,
|
||||||
[GITHUB_TOKEN_COOKIE]: config.configurable?.[GITHUB_TOKEN_COOKIE] ?? "",
|
[GITHUB_TOKEN_COOKIE]: config.configurable?.[GITHUB_TOKEN_COOKIE] ?? "",
|
||||||
[GITHUB_INSTALLATION_TOKEN_COOKIE]:
|
[GITHUB_INSTALLATION_TOKEN_COOKIE]:
|
||||||
config.configurable?.[GITHUB_INSTALLATION_TOKEN_COOKIE] ?? "",
|
config.configurable?.[GITHUB_INSTALLATION_TOKEN_COOKIE] ?? "",
|
||||||
|
[GITHUB_USER_ID_HEADER]:
|
||||||
|
config.configurable?.[GITHUB_USER_ID_HEADER] ?? "",
|
||||||
|
[GITHUB_USER_LOGIN_HEADER]:
|
||||||
|
config.configurable?.[GITHUB_USER_LOGIN_HEADER] ?? "",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ import { createLangGraphClient } from "../../../utils/langgraph-client.js";
|
||||||
import {
|
import {
|
||||||
GITHUB_INSTALLATION_TOKEN_COOKIE,
|
GITHUB_INSTALLATION_TOKEN_COOKIE,
|
||||||
GITHUB_TOKEN_COOKIE,
|
GITHUB_TOKEN_COOKIE,
|
||||||
|
GITHUB_USER_ID_HEADER,
|
||||||
|
GITHUB_USER_LOGIN_HEADER,
|
||||||
} from "@open-swe/shared/constants";
|
} from "@open-swe/shared/constants";
|
||||||
import { createLogger, LogLevel } from "../../../utils/logger.js";
|
import { createLogger, LogLevel } from "../../../utils/logger.js";
|
||||||
import { getBranchName } from "../../../utils/github/git.js";
|
import { getBranchName } from "../../../utils/github/git.js";
|
||||||
|
|
@ -27,6 +29,10 @@ export async function startPlanner(
|
||||||
[GITHUB_TOKEN_COOKIE]: config.configurable?.[GITHUB_TOKEN_COOKIE] ?? "",
|
[GITHUB_TOKEN_COOKIE]: config.configurable?.[GITHUB_TOKEN_COOKIE] ?? "",
|
||||||
[GITHUB_INSTALLATION_TOKEN_COOKIE]:
|
[GITHUB_INSTALLATION_TOKEN_COOKIE]:
|
||||||
config.configurable?.[GITHUB_INSTALLATION_TOKEN_COOKIE] ?? "",
|
config.configurable?.[GITHUB_INSTALLATION_TOKEN_COOKIE] ?? "",
|
||||||
|
[GITHUB_USER_ID_HEADER]:
|
||||||
|
config.configurable?.[GITHUB_USER_ID_HEADER] ?? "",
|
||||||
|
[GITHUB_USER_LOGIN_HEADER]:
|
||||||
|
config.configurable?.[GITHUB_USER_LOGIN_HEADER] ?? "",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ import { getUserRequest } from "../../../utils/user-request.js";
|
||||||
import {
|
import {
|
||||||
GITHUB_INSTALLATION_TOKEN_COOKIE,
|
GITHUB_INSTALLATION_TOKEN_COOKIE,
|
||||||
GITHUB_TOKEN_COOKIE,
|
GITHUB_TOKEN_COOKIE,
|
||||||
|
GITHUB_USER_ID_HEADER,
|
||||||
|
GITHUB_USER_LOGIN_HEADER,
|
||||||
PLAN_INTERRUPT_ACTION_TITLE,
|
PLAN_INTERRUPT_ACTION_TITLE,
|
||||||
PLAN_INTERRUPT_DELIMITER,
|
PLAN_INTERRUPT_DELIMITER,
|
||||||
} from "@open-swe/shared/constants";
|
} from "@open-swe/shared/constants";
|
||||||
|
|
@ -70,6 +72,10 @@ export async function interruptProposedPlan(
|
||||||
[GITHUB_TOKEN_COOKIE]: config.configurable?.[GITHUB_TOKEN_COOKIE] ?? "",
|
[GITHUB_TOKEN_COOKIE]: config.configurable?.[GITHUB_TOKEN_COOKIE] ?? "",
|
||||||
[GITHUB_INSTALLATION_TOKEN_COOKIE]:
|
[GITHUB_INSTALLATION_TOKEN_COOKIE]:
|
||||||
config.configurable?.[GITHUB_INSTALLATION_TOKEN_COOKIE] ?? "",
|
config.configurable?.[GITHUB_INSTALLATION_TOKEN_COOKIE] ?? "",
|
||||||
|
[GITHUB_USER_ID_HEADER]:
|
||||||
|
config.configurable?.[GITHUB_USER_ID_HEADER] ?? "",
|
||||||
|
[GITHUB_USER_LOGIN_HEADER]:
|
||||||
|
config.configurable?.[GITHUB_USER_LOGIN_HEADER] ?? "",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
6
apps/open-swe/src/routes/app.ts
Normal file
6
apps/open-swe/src/routes/app.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { Hono } from "hono";
|
||||||
|
import { issueWebhookHandler } from "./github/issue-webhook.js";
|
||||||
|
|
||||||
|
export const app = new Hono();
|
||||||
|
|
||||||
|
app.post("/webhooks/github", issueWebhookHandler);
|
||||||
195
apps/open-swe/src/routes/github/issue-webhook.ts
Normal file
195
apps/open-swe/src/routes/github/issue-webhook.ts
Normal file
|
|
@ -0,0 +1,195 @@
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
import { Context } from "hono";
|
||||||
|
import { BlankEnv, BlankInput } from "hono/types";
|
||||||
|
import { createLogger, LogLevel } from "../../utils/logger.js";
|
||||||
|
import { GitHubApp } from "../../utils/github-app.js";
|
||||||
|
import { Webhooks } from "@octokit/webhooks";
|
||||||
|
import { createLangGraphClient } from "../../utils/langgraph-client.js";
|
||||||
|
import {
|
||||||
|
GITHUB_INSTALLATION_TOKEN_COOKIE,
|
||||||
|
GITHUB_USER_ID_HEADER,
|
||||||
|
GITHUB_USER_LOGIN_HEADER,
|
||||||
|
MANAGER_GRAPH_ID,
|
||||||
|
} from "@open-swe/shared/constants";
|
||||||
|
import { encryptGitHubToken } from "@open-swe/shared/crypto";
|
||||||
|
import { HumanMessage } from "@langchain/core/messages";
|
||||||
|
import { getOpenSWELabel } from "../../utils/github/label.js";
|
||||||
|
|
||||||
|
const logger = createLogger(LogLevel.INFO, "GitHubIssueWebhook");
|
||||||
|
|
||||||
|
const GITHUB_WEBHOOK_SECRET = process.env.GITHUB_WEBHOOK_SECRET!;
|
||||||
|
|
||||||
|
const githubApp = new GitHubApp();
|
||||||
|
|
||||||
|
const webhooks = new Webhooks({
|
||||||
|
secret: GITHUB_WEBHOOK_SECRET,
|
||||||
|
});
|
||||||
|
|
||||||
|
const getOpenSweAppUrl = (threadId: string) => {
|
||||||
|
if (!process.env.OPEN_SWE_APP_URL) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const baseUrl = new URL(process.env.OPEN_SWE_APP_URL);
|
||||||
|
baseUrl.pathname = `/chat/${threadId}`;
|
||||||
|
return baseUrl.toString();
|
||||||
|
} catch {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPayload = (body: string): Record<string, any> | null => {
|
||||||
|
try {
|
||||||
|
const payload = JSON.parse(body);
|
||||||
|
return payload;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getHeaders = (
|
||||||
|
c: Context,
|
||||||
|
): {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
installationId: string;
|
||||||
|
targetType: string;
|
||||||
|
} | null => {
|
||||||
|
const headers = c.req.header();
|
||||||
|
const webhookId = headers["x-github-delivery"] || "";
|
||||||
|
const webhookEvent = headers["x-github-event"] || "";
|
||||||
|
const installationId = headers["x-github-hook-installation-target-id"] || "";
|
||||||
|
const targetType = headers["x-github-hook-installation-target-type"] || "";
|
||||||
|
if (!webhookId || !webhookEvent || !installationId || !targetType) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return { id: webhookId, name: webhookEvent, installationId, targetType };
|
||||||
|
};
|
||||||
|
|
||||||
|
webhooks.on("issues.labeled", async ({ payload }) => {
|
||||||
|
if (!process.env.GITHUB_TOKEN_ENCRYPTION_KEY) {
|
||||||
|
throw new Error(
|
||||||
|
"GITHUB_TOKEN_ENCRYPTION_KEY environment variable is required",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (payload.label?.name !== getOpenSWELabel()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`'open-swe' label added to issue #${payload.issue.number}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get installation ID from the webhook payload
|
||||||
|
const installationId = payload.installation?.id;
|
||||||
|
|
||||||
|
if (!installationId) {
|
||||||
|
logger.error("No installation ID found in webhook payload");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [octokit, { token }] = await Promise.all([
|
||||||
|
githubApp.getInstallationOctokit(installationId),
|
||||||
|
githubApp.getInstallationAccessToken(installationId),
|
||||||
|
]);
|
||||||
|
const issueData = {
|
||||||
|
owner: payload.repository.owner.login,
|
||||||
|
repo: payload.repository.name,
|
||||||
|
issueNumber: payload.issue.number,
|
||||||
|
issueTitle: payload.issue.title,
|
||||||
|
issueBody: payload.issue.body || "",
|
||||||
|
userId: payload.sender.id,
|
||||||
|
userLogin: payload.sender.login,
|
||||||
|
};
|
||||||
|
|
||||||
|
const langGraphClient = createLangGraphClient({
|
||||||
|
defaultHeaders: {
|
||||||
|
[GITHUB_INSTALLATION_TOKEN_COOKIE]: encryptGitHubToken(
|
||||||
|
token,
|
||||||
|
process.env.GITHUB_TOKEN_ENCRYPTION_KEY,
|
||||||
|
),
|
||||||
|
[GITHUB_USER_ID_HEADER]: issueData.userId.toString(),
|
||||||
|
[GITHUB_USER_LOGIN_HEADER]: issueData.userLogin,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const threadId = uuidv4();
|
||||||
|
const run = await langGraphClient.runs.create(threadId, MANAGER_GRAPH_ID, {
|
||||||
|
input: {
|
||||||
|
messages: [
|
||||||
|
new HumanMessage({
|
||||||
|
id: uuidv4(),
|
||||||
|
content: `**${issueData.issueTitle}**\n\n${issueData.issueBody}`,
|
||||||
|
additional_kwargs: {
|
||||||
|
isOriginalIssue: true,
|
||||||
|
githubIssueId: issueData.issueNumber,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
githubIssueId: issueData.issueNumber,
|
||||||
|
targetRepository: {
|
||||||
|
owner: issueData.owner,
|
||||||
|
repo: issueData.repo,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
recursion_limit: 400,
|
||||||
|
},
|
||||||
|
ifNotExists: "create",
|
||||||
|
streamResumable: true,
|
||||||
|
streamMode: ["values", "messages", "custom"],
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info("Created new run from GitHub issue.", {
|
||||||
|
thread_id: threadId,
|
||||||
|
run_id: run.run_id,
|
||||||
|
issue_number: issueData.issueNumber,
|
||||||
|
owner: issueData.owner,
|
||||||
|
repo: issueData.repo,
|
||||||
|
user_id: issueData.userId,
|
||||||
|
user_login: issueData.userLogin,
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info("Creating comment...");
|
||||||
|
const appUrl = getOpenSweAppUrl(threadId);
|
||||||
|
await octokit.request(
|
||||||
|
"POST /repos/{owner}/{repo}/issues/{issue_number}/comments",
|
||||||
|
{
|
||||||
|
owner: issueData.owner,
|
||||||
|
repo: issueData.repo,
|
||||||
|
issue_number: issueData.issueNumber,
|
||||||
|
body: `🤖 Open SWE has been triggered for this issue. Processing...\n\n${appUrl ? `View run in Open SWE [here](${appUrl})` : ""}`,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Error processing webhook:", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function issueWebhookHandler(
|
||||||
|
c: Context<BlankEnv, "/webhooks/github", BlankInput>,
|
||||||
|
) {
|
||||||
|
const payload = getPayload(await c.req.text());
|
||||||
|
if (!payload) {
|
||||||
|
logger.error("Missing payload");
|
||||||
|
return c.json({ error: "Missing payload" }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventHeaders = getHeaders(c);
|
||||||
|
if (!eventHeaders) {
|
||||||
|
logger.error("Missing webhook headers");
|
||||||
|
return c.json({ error: "Missing webhook headers" }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await webhooks.receive({
|
||||||
|
id: eventHeaders.id,
|
||||||
|
name: eventHeaders.name as any,
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
|
||||||
|
return c.json({ received: true });
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Webhook error:", error);
|
||||||
|
return c.json({ error: "Webhook processing failed" }, { status: 400 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,38 +2,17 @@ import { Auth, HTTPException } from "@langchain/langgraph-sdk/auth";
|
||||||
import {
|
import {
|
||||||
verifyGithubUser,
|
verifyGithubUser,
|
||||||
GithubUser,
|
GithubUser,
|
||||||
|
verifyGithubUserId,
|
||||||
} from "@open-swe/shared/github/verify-user";
|
} from "@open-swe/shared/github/verify-user";
|
||||||
import {
|
import {
|
||||||
GITHUB_INSTALLATION_TOKEN_COOKIE,
|
GITHUB_INSTALLATION_TOKEN_COOKIE,
|
||||||
GITHUB_TOKEN_COOKIE,
|
GITHUB_TOKEN_COOKIE,
|
||||||
|
GITHUB_USER_ID_HEADER,
|
||||||
|
GITHUB_USER_LOGIN_HEADER,
|
||||||
} from "@open-swe/shared/constants";
|
} from "@open-swe/shared/constants";
|
||||||
import { decryptGitHubToken } from "@open-swe/shared/crypto";
|
import { decryptGitHubToken } from "@open-swe/shared/crypto";
|
||||||
|
import { verifyGitHubWebhookOrThrow } from "./github.js";
|
||||||
const STUDIO_USER_ID = "langgraph-studio-user";
|
import { createWithOwnerMetadata, createOwnerFilter } from "./utils.js";
|
||||||
|
|
||||||
// Helper function to check if user is studio user
|
|
||||||
const isStudioUser = (userIdentity: string): boolean => {
|
|
||||||
return userIdentity === STUDIO_USER_ID;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Helper function for operations that only need owner filtering
|
|
||||||
const createOwnerFilter = (user: { identity: string }) => {
|
|
||||||
if (isStudioUser(user.identity)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return { owner: user.identity };
|
|
||||||
};
|
|
||||||
|
|
||||||
// Helper function for create operations that set metadata
|
|
||||||
const createWithOwnerMetadata = (value: any, user: { identity: string }) => {
|
|
||||||
if (isStudioUser(user.identity)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
value.metadata ??= {};
|
|
||||||
value.metadata.owner = user.identity;
|
|
||||||
return { owner: user.identity };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const auth = new Auth()
|
export const auth = new Auth()
|
||||||
.authenticate(async (request: Request) => {
|
.authenticate(async (request: Request) => {
|
||||||
|
|
@ -45,6 +24,13 @@ export const auth = new Auth()
|
||||||
display_name: "CORS Preflight",
|
display_name: "CORS Preflight",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ghSecretHashHeader = request.headers.get("X-Hub-Signature-256");
|
||||||
|
if (ghSecretHashHeader) {
|
||||||
|
// This will either return a valid user, or throw an error
|
||||||
|
return await verifyGitHubWebhookOrThrow(request);
|
||||||
|
}
|
||||||
|
|
||||||
const encryptionKey = process.env.GITHUB_TOKEN_ENCRYPTION_KEY;
|
const encryptionKey = process.env.GITHUB_TOKEN_ENCRYPTION_KEY;
|
||||||
if (!encryptionKey) {
|
if (!encryptionKey) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|
@ -52,13 +38,6 @@ export const auth = new Auth()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse Authorization header
|
|
||||||
const encryptedAccessToken = request.headers.get(GITHUB_TOKEN_COOKIE);
|
|
||||||
if (!encryptedAccessToken) {
|
|
||||||
throw new HTTPException(401, {
|
|
||||||
message: "GitHub access token header missing",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// We don't do anything with this token right now, but still confirm it
|
// We don't do anything with this token right now, but still confirm it
|
||||||
// exists as it will cause issues later on if it's not present.
|
// exists as it will cause issues later on if it's not present.
|
||||||
const encryptedInstallationToken = request.headers.get(
|
const encryptedInstallationToken = request.headers.get(
|
||||||
|
|
@ -70,24 +49,37 @@ export const auth = new Auth()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate GitHub access token
|
|
||||||
let user: GithubUser | undefined;
|
let user: GithubUser | undefined;
|
||||||
try {
|
|
||||||
|
const encryptedAccessToken = request.headers.get(GITHUB_TOKEN_COOKIE);
|
||||||
|
if (!encryptedAccessToken) {
|
||||||
|
// If there isn't a user access token, check to see if the user info is in headers.
|
||||||
|
// This would indicate a bot created the request.
|
||||||
|
const userIdHeader = request.headers.get(GITHUB_USER_ID_HEADER);
|
||||||
|
const userLoginHeader = request.headers.get(GITHUB_USER_LOGIN_HEADER);
|
||||||
|
if (!userIdHeader || !userLoginHeader) {
|
||||||
|
throw new HTTPException(401, {
|
||||||
|
message: "Github-User-Id or Github-User-Login header missing",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
user = await verifyGithubUserId(
|
||||||
|
decryptGitHubToken(encryptedInstallationToken, encryptionKey),
|
||||||
|
Number(userIdHeader),
|
||||||
|
userLoginHeader,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
// Ensure we decrypt the token before passing to the verification function.
|
// Ensure we decrypt the token before passing to the verification function.
|
||||||
user = await verifyGithubUser(
|
user = await verifyGithubUser(
|
||||||
decryptGitHubToken(encryptedAccessToken, encryptionKey),
|
decryptGitHubToken(encryptedAccessToken, encryptionKey),
|
||||||
);
|
);
|
||||||
if (!user) {
|
}
|
||||||
throw new HTTPException(401, {
|
|
||||||
message:
|
if (!user) {
|
||||||
"Invalid GitHub token or user is not a member of the required organization.",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e: any) {
|
|
||||||
throw new HTTPException(401, {
|
throw new HTTPException(401, {
|
||||||
message: `Authentication error: ${e.message}`,
|
message: "User not found",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
identity: user.id.toString(),
|
identity: user.id.toString(),
|
||||||
is_authenticated: true,
|
is_authenticated: true,
|
||||||
|
|
|
||||||
58
apps/open-swe/src/security/github.ts
Normal file
58
apps/open-swe/src/security/github.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { HTTPException } from "@langchain/langgraph-sdk/auth";
|
||||||
|
import { Webhooks } from "@octokit/webhooks";
|
||||||
|
import { createLogger, LogLevel } from "../utils/logger.js";
|
||||||
|
|
||||||
|
const logger = createLogger(LogLevel.INFO, "GitHubWebhookAuth");
|
||||||
|
|
||||||
|
export async function verifyGitHubWebhookOrThrow(request: Request) {
|
||||||
|
const secret = process.env.GITHUB_WEBHOOK_SECRET;
|
||||||
|
if (!secret) {
|
||||||
|
throw new Error("Missing GITHUB_WEBHOOK_SECRET environment variable.");
|
||||||
|
}
|
||||||
|
const webhooks = new Webhooks({
|
||||||
|
secret,
|
||||||
|
});
|
||||||
|
|
||||||
|
const requestClone = request.clone();
|
||||||
|
|
||||||
|
const githubDeliveryHeader = requestClone.headers.get("x-github-delivery");
|
||||||
|
const githubEventHeader = requestClone.headers.get("x-github-event");
|
||||||
|
const githubSignatureHeader = requestClone.headers.get("x-hub-signature-256");
|
||||||
|
if (!githubDeliveryHeader || !githubEventHeader || !githubSignatureHeader) {
|
||||||
|
throw new HTTPException(401, {
|
||||||
|
message: "Missing GitHub webhook headers.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = await requestClone.text();
|
||||||
|
const signature = await webhooks.sign(payload);
|
||||||
|
const isValid = await webhooks.verify(payload, signature);
|
||||||
|
if (!isValid) {
|
||||||
|
logger.error("Failed to verify GitHub webhook");
|
||||||
|
throw new HTTPException(401, {
|
||||||
|
message: "Invalid GitHub webhook signature.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
identity: "x-internal-github-bot",
|
||||||
|
is_authenticated: true,
|
||||||
|
display_name: "GitHub Bot",
|
||||||
|
permissions: [
|
||||||
|
"threads:create",
|
||||||
|
"threads:create_run",
|
||||||
|
"threads:read",
|
||||||
|
"threads:delete",
|
||||||
|
"threads:update",
|
||||||
|
"threads:search",
|
||||||
|
"assistants:create",
|
||||||
|
"assistants:read",
|
||||||
|
"assistants:delete",
|
||||||
|
"assistants:update",
|
||||||
|
"assistants:search",
|
||||||
|
"deployments:read",
|
||||||
|
"deployments:search",
|
||||||
|
"store:access",
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
28
apps/open-swe/src/security/utils.ts
Normal file
28
apps/open-swe/src/security/utils.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
const STUDIO_USER_ID = "langgraph-studio-user";
|
||||||
|
|
||||||
|
// Helper function to check if user is studio user
|
||||||
|
export function isStudioUser(userIdentity: string): boolean {
|
||||||
|
return userIdentity === STUDIO_USER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function for operations that only need owner filtering
|
||||||
|
export function createOwnerFilter(user: { identity: string }) {
|
||||||
|
if (isStudioUser(user.identity)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return { owner: user.identity };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function for create operations that set metadata
|
||||||
|
export function createWithOwnerMetadata(
|
||||||
|
value: any,
|
||||||
|
user: { identity: string },
|
||||||
|
) {
|
||||||
|
if (isStudioUser(user.identity)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
value.metadata ??= {};
|
||||||
|
value.metadata.owner = user.identity;
|
||||||
|
return { owner: user.identity };
|
||||||
|
}
|
||||||
46
apps/open-swe/src/utils/github-app.ts
Normal file
46
apps/open-swe/src/utils/github-app.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { App } from "@octokit/app";
|
||||||
|
import { Octokit } from "@octokit/core";
|
||||||
|
|
||||||
|
export class GitHubApp {
|
||||||
|
app: App;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
const appId = process.env.GITHUB_APP_ID;
|
||||||
|
const privateKey = process.env.GITHUB_APP_PRIVATE_KEY;
|
||||||
|
const webhookSecret = process.env.GITHUB_WEBHOOK_SECRET;
|
||||||
|
if (!appId || !privateKey || !webhookSecret) {
|
||||||
|
throw new Error(
|
||||||
|
"GitHub App ID, Private Key, or Webhook Secret is not configured.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.app = new App({
|
||||||
|
appId,
|
||||||
|
privateKey,
|
||||||
|
webhooks: {
|
||||||
|
secret: webhookSecret,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getInstallationOctokit(installationId: number): Promise<Octokit> {
|
||||||
|
return await this.app.getInstallationOctokit(installationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getInstallationAccessToken(installationId: number): Promise<{
|
||||||
|
token: string;
|
||||||
|
expiresAt: string;
|
||||||
|
}> {
|
||||||
|
const octokit = await this.app.getInstallationOctokit(installationId);
|
||||||
|
|
||||||
|
// The installation access token is available on the auth property
|
||||||
|
const auth = (await octokit.auth({
|
||||||
|
type: "installation",
|
||||||
|
})) as any;
|
||||||
|
|
||||||
|
return {
|
||||||
|
token: auth.token,
|
||||||
|
expiresAt: auth.expiresAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,9 +15,9 @@ export function getGitHubTokensFromConfig(config: GraphConfig): {
|
||||||
const encryptedGitHubToken = config.configurable[GITHUB_TOKEN_COOKIE];
|
const encryptedGitHubToken = config.configurable[GITHUB_TOKEN_COOKIE];
|
||||||
const encryptedInstallationToken =
|
const encryptedInstallationToken =
|
||||||
config.configurable[GITHUB_INSTALLATION_TOKEN_COOKIE];
|
config.configurable[GITHUB_INSTALLATION_TOKEN_COOKIE];
|
||||||
if (!encryptedGitHubToken || !encryptedInstallationToken) {
|
if (!encryptedInstallationToken) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Missing required x-github-access-token or x-github-installation-token in configuration.",
|
`Missing required ${GITHUB_INSTALLATION_TOKEN_COOKIE} in configuration.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -30,10 +30,9 @@ export function getGitHubTokensFromConfig(config: GraphConfig): {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrypt the GitHub token
|
// Decrypt the GitHub token
|
||||||
const githubAccessToken = decryptGitHubToken(
|
const githubAccessToken = encryptedGitHubToken
|
||||||
encryptedGitHubToken,
|
? decryptGitHubToken(encryptedGitHubToken, encryptionKey)
|
||||||
encryptionKey,
|
: "";
|
||||||
);
|
|
||||||
const githubInstallationToken = decryptGitHubToken(
|
const githubInstallationToken = decryptGitHubToken(
|
||||||
encryptedInstallationToken,
|
encryptedInstallationToken,
|
||||||
encryptionKey,
|
encryptionKey,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { Octokit } from "@octokit/rest";
|
import { Octokit } from "@octokit/rest";
|
||||||
import { createLogger, LogLevel } from "../logger.js";
|
import { createLogger, LogLevel } from "../logger.js";
|
||||||
import { GitHubIssue, GitHubIssueComment, GitHubPullRequest } from "./types.js";
|
import { GitHubIssue, GitHubIssueComment, GitHubPullRequest } from "./types.js";
|
||||||
|
import { getOpenSWELabel } from "./label.js";
|
||||||
|
|
||||||
const logger = createLogger(LogLevel.INFO, "GitHub-API");
|
const logger = createLogger(LogLevel.INFO, "GitHub-API");
|
||||||
|
|
||||||
|
|
@ -141,7 +142,7 @@ export async function createPullRequest({
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
issue_number: pullRequest.number,
|
issue_number: pullRequest.number,
|
||||||
labels: ["open-swe"],
|
labels: [getOpenSWELabel()],
|
||||||
});
|
});
|
||||||
logger.info("Added 'open-swe' label to pull request", {
|
logger.info("Added 'open-swe' label to pull request", {
|
||||||
pullRequestNumber: pullRequest.number,
|
pullRequestNumber: pullRequest.number,
|
||||||
|
|
|
||||||
6
apps/open-swe/src/utils/github/label.ts
Normal file
6
apps/open-swe/src/utils/github/label.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
/**
|
||||||
|
* @returns "open-swe" or "open-swe-dev" based on the NODE_ENV.
|
||||||
|
*/
|
||||||
|
export function getOpenSWELabel() {
|
||||||
|
return process.env.NODE_ENV === "production" ? "open-swe" : "open-swe-dev";
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import { ThreadViewLoading } from "@/components/v2/thread-view-loading";
|
||||||
import { ThreadDisplayInfo, threadToDisplayInfo } from "@/components/v2/types";
|
import { ThreadDisplayInfo, threadToDisplayInfo } from "@/components/v2/types";
|
||||||
import { useThreads } from "@/hooks/useThreads";
|
import { useThreads } from "@/hooks/useThreads";
|
||||||
import { useStream } from "@langchain/langgraph-sdk/react";
|
import { useStream } from "@langchain/langgraph-sdk/react";
|
||||||
|
import { MANAGER_GRAPH_ID } from "@open-swe/shared/constants";
|
||||||
import { ManagerGraphState } from "@open-swe/shared/open-swe/manager/types";
|
import { ManagerGraphState } from "@open-swe/shared/open-swe/manager/types";
|
||||||
import { GraphState } from "@open-swe/shared/open-swe/types";
|
import { GraphState } from "@open-swe/shared/open-swe/types";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
|
@ -24,14 +25,12 @@ export default function ThreadPage({
|
||||||
const { thread_id } = use(params);
|
const { thread_id } = use(params);
|
||||||
const stream = useStream<ManagerGraphState>({
|
const stream = useStream<ManagerGraphState>({
|
||||||
apiUrl: process.env.NEXT_PUBLIC_API_URL ?? "",
|
apiUrl: process.env.NEXT_PUBLIC_API_URL ?? "",
|
||||||
assistantId: process.env.NEXT_PUBLIC_MANAGER_ASSISTANT_ID ?? "",
|
assistantId: MANAGER_GRAPH_ID,
|
||||||
threadId: thread_id,
|
threadId: thread_id,
|
||||||
reconnectOnMount: true,
|
reconnectOnMount: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { threads, threadsLoading } = useThreads<GraphState>(
|
const { threads, threadsLoading } = useThreads<GraphState>(MANAGER_GRAPH_ID);
|
||||||
process.env.NEXT_PUBLIC_MANAGER_ASSISTANT_ID,
|
|
||||||
);
|
|
||||||
// Find the thread by ID
|
// Find the thread by ID
|
||||||
const thread = threads.find((t) => t.thread_id === thread_id);
|
const thread = threads.find((t) => t.thread_id === thread_id);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,10 @@ import { GitHubAppProvider } from "@/providers/GitHubApp";
|
||||||
import { GraphState } from "@open-swe/shared/open-swe/types";
|
import { GraphState } from "@open-swe/shared/open-swe/types";
|
||||||
import { Toaster } from "@/components/ui/sonner";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
|
import { MANAGER_GRAPH_ID } from "@open-swe/shared/constants";
|
||||||
|
|
||||||
export default function ChatPage() {
|
export default function ChatPage() {
|
||||||
const { threads, threadsLoading } = useThreads<GraphState>(
|
const { threads, threadsLoading } = useThreads<GraphState>(MANAGER_GRAPH_ID);
|
||||||
process.env.NEXT_PUBLIC_MANAGER_ASSISTANT_ID,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Convert Thread objects to ThreadDisplayInfo for UI
|
// Convert Thread objects to ThreadDisplayInfo for UI
|
||||||
const displayThreads: ThreadDisplayInfo[] = threads.map(threadToDisplayInfo);
|
const displayThreads: ThreadDisplayInfo[] = threads.map(threadToDisplayInfo);
|
||||||
|
|
|
||||||
|
|
@ -12,14 +12,13 @@ import { useThreads } from "@/hooks/useThreads";
|
||||||
import { GraphState } from "@open-swe/shared/open-swe/types";
|
import { GraphState } from "@open-swe/shared/open-swe/types";
|
||||||
import { ThreadCard, ThreadCardLoading } from "@/components/v2/thread-card";
|
import { ThreadCard, ThreadCardLoading } from "@/components/v2/thread-card";
|
||||||
import { ThemeToggle } from "@/components/theme-toggle";
|
import { ThemeToggle } from "@/components/theme-toggle";
|
||||||
|
import { MANAGER_GRAPH_ID } from "@open-swe/shared/constants";
|
||||||
|
|
||||||
type FilterStatus = "all" | "running" | "completed" | "failed" | "pending";
|
type FilterStatus = "all" | "running" | "completed" | "failed" | "pending";
|
||||||
|
|
||||||
export default function AllThreadsPage() {
|
export default function AllThreadsPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { threads, threadsLoading } = useThreads<GraphState>(
|
const { threads, threadsLoading } = useThreads<GraphState>(MANAGER_GRAPH_ID);
|
||||||
process.env.NEXT_PUBLIC_MANAGER_ASSISTANT_ID,
|
|
||||||
);
|
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [statusFilter, setStatusFilter] = useState<FilterStatus>("all");
|
const [statusFilter, setStatusFilter] = useState<FilterStatus>("all");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import { GitHubInstallationBanner } from "../github/installation-banner";
|
||||||
import { QuickActions } from "./quick-actions";
|
import { QuickActions } from "./quick-actions";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { GitHubLogoutButton } from "../github/github-oauth-button";
|
import { GitHubLogoutButton } from "../github/github-oauth-button";
|
||||||
|
import { MANAGER_GRAPH_ID } from "@open-swe/shared/constants";
|
||||||
|
|
||||||
interface DefaultViewProps {
|
interface DefaultViewProps {
|
||||||
threads: ThreadDisplayInfo[];
|
threads: ThreadDisplayInfo[];
|
||||||
|
|
@ -37,8 +38,7 @@ export function DefaultView({ threads, threadsLoading }: DefaultViewProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [quickActionPrompt, setQuickActionPrompt] = useState("");
|
const [quickActionPrompt, setQuickActionPrompt] = useState("");
|
||||||
const apiUrl: string | undefined = process.env.NEXT_PUBLIC_API_URL ?? "";
|
const apiUrl: string | undefined = process.env.NEXT_PUBLIC_API_URL ?? "";
|
||||||
const assistantId: string | undefined =
|
const assistantId: string | undefined = MANAGER_GRAPH_ID;
|
||||||
process.env.NEXT_PUBLIC_MANAGER_ASSISTANT_ID ?? "";
|
|
||||||
const {
|
const {
|
||||||
contentBlocks,
|
contentBlocks,
|
||||||
setContentBlocks,
|
setContentBlocks,
|
||||||
|
|
@ -49,8 +49,8 @@ export function DefaultView({ threads, threadsLoading }: DefaultViewProps) {
|
||||||
handlePaste,
|
handlePaste,
|
||||||
} = useFileUpload();
|
} = useFileUpload();
|
||||||
|
|
||||||
if (!apiUrl || !assistantId) {
|
if (!apiUrl) {
|
||||||
return <div>Missing API URL or Assistant ID</div>;
|
return <div>Missing API URL environment variable</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import { GraphState } from "@open-swe/shared/open-swe/types";
|
||||||
import { Base64ContentBlock, HumanMessage } from "@langchain/core/messages";
|
import { Base64ContentBlock, HumanMessage } from "@langchain/core/messages";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { DEFAULT_CONFIG_KEY, useConfigStore } from "@/hooks/useConfigStore";
|
import { DEFAULT_CONFIG_KEY, useConfigStore } from "@/hooks/useConfigStore";
|
||||||
|
import { MANAGER_GRAPH_ID } from "@open-swe/shared/constants";
|
||||||
|
|
||||||
interface TerminalInputProps {
|
interface TerminalInputProps {
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
|
@ -51,14 +52,7 @@ export function TerminalInput({
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSend = async () => {
|
const handleSend = async () => {
|
||||||
const assistantId = process.env.NEXT_PUBLIC_MANAGER_ASSISTANT_ID;
|
const assistantId = MANAGER_GRAPH_ID;
|
||||||
if (!assistantId) {
|
|
||||||
toast.error("No assistant ID found", {
|
|
||||||
richColors: true,
|
|
||||||
closeButton: true,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!selectedRepository) {
|
if (!selectedRepository) {
|
||||||
toast.error("Please select a repository first", {
|
toast.error("Please select a repository first", {
|
||||||
richColors: true,
|
richColors: true,
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,11 @@ import { GraphState } from "@open-swe/shared/open-swe/types";
|
||||||
import { ActionsRenderer } from "./actions-renderer";
|
import { ActionsRenderer } from "./actions-renderer";
|
||||||
import { ThemeToggle } from "../theme-toggle";
|
import { ThemeToggle } from "../theme-toggle";
|
||||||
import { HumanMessage } from "@langchain/core/messages";
|
import { HumanMessage } from "@langchain/core/messages";
|
||||||
import { DO_NOT_RENDER_ID_PREFIX } from "@open-swe/shared/constants";
|
import {
|
||||||
|
DO_NOT_RENDER_ID_PREFIX,
|
||||||
|
PROGRAMMER_GRAPH_ID,
|
||||||
|
PLANNER_GRAPH_ID,
|
||||||
|
} from "@open-swe/shared/constants";
|
||||||
import { StickToBottom } from "use-stick-to-bottom";
|
import { StickToBottom } from "use-stick-to-bottom";
|
||||||
import {
|
import {
|
||||||
StickyToBottomContent,
|
StickyToBottomContent,
|
||||||
|
|
@ -23,9 +27,6 @@ import {
|
||||||
} from "../../utils/scroll-utils";
|
} from "../../utils/scroll-utils";
|
||||||
import { ManagerChat } from "./manager-chat";
|
import { ManagerChat } from "./manager-chat";
|
||||||
|
|
||||||
const PROGRAMMER_ASSISTANT_ID = process.env.NEXT_PUBLIC_PROGRAMMER_ASSISTANT_ID;
|
|
||||||
const PLANNER_ASSISTANT_ID = process.env.NEXT_PUBLIC_PLANNER_ASSISTANT_ID;
|
|
||||||
|
|
||||||
interface ThreadViewProps {
|
interface ThreadViewProps {
|
||||||
stream: ReturnType<typeof useStream<ManagerGraphState>>;
|
stream: ReturnType<typeof useStream<ManagerGraphState>>;
|
||||||
displayThread: ThreadDisplayInfo;
|
displayThread: ThreadDisplayInfo;
|
||||||
|
|
@ -153,23 +154,17 @@ export function ThreadView({
|
||||||
<TabsContent value="planner">
|
<TabsContent value="planner">
|
||||||
<Card className="border-border bg-card px-0 py-4 dark:bg-gray-950">
|
<Card className="border-border bg-card px-0 py-4 dark:bg-gray-950">
|
||||||
<CardContent className="space-y-2 p-3 pt-0">
|
<CardContent className="space-y-2 p-3 pt-0">
|
||||||
{plannerThreadId &&
|
{plannerThreadId && plannerRunId && (
|
||||||
plannerRunId &&
|
<ActionsRenderer<PlannerGraphState>
|
||||||
PLANNER_ASSISTANT_ID && (
|
graphId={PLANNER_GRAPH_ID}
|
||||||
<ActionsRenderer<PlannerGraphState>
|
threadId={plannerThreadId}
|
||||||
graphId={PLANNER_ASSISTANT_ID}
|
runId={plannerRunId}
|
||||||
threadId={plannerThreadId}
|
setProgrammerSession={setProgrammerSession}
|
||||||
runId={plannerRunId}
|
programmerSession={programmerSession}
|
||||||
setProgrammerSession={setProgrammerSession}
|
setSelectedTab={setSelectedTab}
|
||||||
programmerSession={programmerSession}
|
/>
|
||||||
setSelectedTab={setSelectedTab}
|
)}
|
||||||
/>
|
{!(plannerThreadId && plannerRunId) && (
|
||||||
)}
|
|
||||||
{!(
|
|
||||||
plannerThreadId &&
|
|
||||||
plannerRunId &&
|
|
||||||
PLANNER_ASSISTANT_ID
|
|
||||||
) && (
|
|
||||||
<div className="flex items-center justify-center gap-2 py-8">
|
<div className="flex items-center justify-center gap-2 py-8">
|
||||||
<Clock className="text-muted-foreground size-4" />
|
<Clock className="text-muted-foreground size-4" />
|
||||||
<span className="text-muted-foreground text-sm">
|
<span className="text-muted-foreground text-sm">
|
||||||
|
|
@ -183,9 +178,9 @@ export function ThreadView({
|
||||||
<TabsContent value="programmer">
|
<TabsContent value="programmer">
|
||||||
<Card className="border-border bg-card px-0 py-4 dark:bg-gray-950">
|
<Card className="border-border bg-card px-0 py-4 dark:bg-gray-950">
|
||||||
<CardContent className="space-y-2 p-3 pt-0">
|
<CardContent className="space-y-2 p-3 pt-0">
|
||||||
{programmerSession && PROGRAMMER_ASSISTANT_ID && (
|
{programmerSession && (
|
||||||
<ActionsRenderer<GraphState>
|
<ActionsRenderer<GraphState>
|
||||||
graphId={PROGRAMMER_ASSISTANT_ID}
|
graphId={PROGRAMMER_GRAPH_ID}
|
||||||
threadId={programmerSession.threadId}
|
threadId={programmerSession.threadId}
|
||||||
runId={programmerSession.runId}
|
runId={programmerSession.runId}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import {
|
||||||
CustomNodeEvent,
|
CustomNodeEvent,
|
||||||
isCustomNodeEvent,
|
isCustomNodeEvent,
|
||||||
} from "@open-swe/shared/open-swe/custom-node-events";
|
} from "@open-swe/shared/open-swe/custom-node-events";
|
||||||
|
import { MANAGER_GRAPH_ID } from "@open-swe/shared/constants";
|
||||||
|
|
||||||
const useTypedStream = useStream<
|
const useTypedStream = useStream<
|
||||||
GraphState,
|
GraphState,
|
||||||
|
|
@ -37,18 +38,15 @@ const StreamSession = ({ children }: { children: ReactNode }) => {
|
||||||
const [customEvents, setCustomEvents] = useState<CustomNodeEvent[]>([]);
|
const [customEvents, setCustomEvents] = useState<CustomNodeEvent[]>([]);
|
||||||
const { refreshThreads } = useThreadsContext();
|
const { refreshThreads } = useThreadsContext();
|
||||||
|
|
||||||
if (
|
if (!process.env.NEXT_PUBLIC_API_URL) {
|
||||||
!process.env.NEXT_PUBLIC_API_URL ||
|
|
||||||
!process.env.NEXT_PUBLIC_MANAGER_ASSISTANT_ID
|
|
||||||
) {
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Both NEXT_PUBLIC_API_URL and NEXT_PUBLIC_MANAGER_ASSISTANT_ID environment variables must be defined.",
|
"NEXT_PUBLIC_API_URL environment variable must be defined.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const streamValue = useTypedStream({
|
const streamValue = useTypedStream({
|
||||||
apiUrl: process.env.NEXT_PUBLIC_API_URL,
|
apiUrl: process.env.NEXT_PUBLIC_API_URL,
|
||||||
assistantId: process.env.NEXT_PUBLIC_MANAGER_ASSISTANT_ID,
|
assistantId: MANAGER_GRAPH_ID,
|
||||||
reconnectOnMount: true,
|
reconnectOnMount: true,
|
||||||
threadId: threadId ?? null,
|
threadId: threadId ?? null,
|
||||||
onCustomEvent: (event, options) => {
|
onCustomEvent: (event, options) => {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@
|
||||||
"dependencies": ["./apps/open-swe"],
|
"dependencies": ["./apps/open-swe"],
|
||||||
"auth": {
|
"auth": {
|
||||||
"path": "./apps/open-swe/src/security/auth.ts:auth"
|
"path": "./apps/open-swe/src/security/auth.ts:auth"
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"app": "./apps/open-swe/src/routes/app.ts:app"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -12,3 +12,10 @@ export const DO_NOT_RENDER_ID_PREFIX = "do-not-render-";
|
||||||
export const GITHUB_AUTH_STATE_COOKIE = "github_auth_state";
|
export const GITHUB_AUTH_STATE_COOKIE = "github_auth_state";
|
||||||
export const GITHUB_INSTALLATION_ID_COOKIE = "github_installation_id";
|
export const GITHUB_INSTALLATION_ID_COOKIE = "github_installation_id";
|
||||||
export const GITHUB_TOKEN_TYPE_COOKIE = "github_token_type";
|
export const GITHUB_TOKEN_TYPE_COOKIE = "github_token_type";
|
||||||
|
|
||||||
|
export const MANAGER_GRAPH_ID = "manager";
|
||||||
|
export const PLANNER_GRAPH_ID = "planner";
|
||||||
|
export const PROGRAMMER_GRAPH_ID = "programmer";
|
||||||
|
|
||||||
|
export const GITHUB_USER_ID_HEADER = "x-github-user-id";
|
||||||
|
export const GITHUB_USER_LOGIN_HEADER = "x-github-user-login";
|
||||||
|
|
|
||||||
|
|
@ -26,3 +26,31 @@ export async function verifyGithubUser(
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies a GitHub user ID using the app installation token. Checks that the provided
|
||||||
|
* user ID is valid, and the provided login matches the user's login.
|
||||||
|
* @param installationToken The GitHub installation token.
|
||||||
|
* @param userId The GitHub user ID.
|
||||||
|
* @param userLogin The GitHub user login.
|
||||||
|
* @returns A promise that resolves with the user object if valid, otherwise undefined.
|
||||||
|
*/
|
||||||
|
export async function verifyGithubUserId(
|
||||||
|
installationToken: string,
|
||||||
|
userId: number,
|
||||||
|
userLogin: string,
|
||||||
|
): Promise<GithubUser | undefined> {
|
||||||
|
try {
|
||||||
|
const octokit = new Octokit({ auth: installationToken });
|
||||||
|
const { data: user } = await octokit.users.getById({ account_id: userId });
|
||||||
|
if (!user || !user.login) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (user.login !== userLogin) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ import {
|
||||||
import {
|
import {
|
||||||
GITHUB_INSTALLATION_TOKEN_COOKIE,
|
GITHUB_INSTALLATION_TOKEN_COOKIE,
|
||||||
GITHUB_TOKEN_COOKIE,
|
GITHUB_TOKEN_COOKIE,
|
||||||
|
GITHUB_USER_ID_HEADER,
|
||||||
|
GITHUB_USER_LOGIN_HEADER,
|
||||||
} from "../constants.js";
|
} from "../constants.js";
|
||||||
import { withLangGraph } from "@langchain/langgraph/zod";
|
import { withLangGraph } from "@langchain/langgraph/zod";
|
||||||
import { BaseMessage } from "@langchain/core/messages";
|
import { BaseMessage } from "@langchain/core/messages";
|
||||||
|
|
@ -395,6 +397,16 @@ export const GraphConfigurationMetadata: {
|
||||||
type: "hidden",
|
type: "hidden",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
[GITHUB_USER_ID_HEADER]: {
|
||||||
|
x_open_swe_ui_config: {
|
||||||
|
type: "hidden",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[GITHUB_USER_LOGIN_HEADER]: {
|
||||||
|
x_open_swe_ui_config: {
|
||||||
|
type: "hidden",
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GraphConfiguration = z.object({
|
export const GraphConfiguration = z.object({
|
||||||
|
|
@ -550,6 +562,20 @@ export const GraphConfiguration = z.object({
|
||||||
.langgraph.metadata(
|
.langgraph.metadata(
|
||||||
GraphConfigurationMetadata[GITHUB_INSTALLATION_TOKEN_COOKIE],
|
GraphConfigurationMetadata[GITHUB_INSTALLATION_TOKEN_COOKIE],
|
||||||
),
|
),
|
||||||
|
/**
|
||||||
|
* The user's GitHub ID. Required when creating runs triggered by a bot (e.g. GitHub issue)
|
||||||
|
*/
|
||||||
|
[GITHUB_USER_ID_HEADER]: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.langgraph.metadata(GraphConfigurationMetadata[GITHUB_USER_ID_HEADER]),
|
||||||
|
/**
|
||||||
|
* The user's GitHub login. Required when creating runs triggered by a bot (e.g. GitHub issue)
|
||||||
|
*/
|
||||||
|
[GITHUB_USER_LOGIN_HEADER]: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.langgraph.metadata(GraphConfigurationMetadata[GITHUB_USER_LOGIN_HEADER]),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type GraphConfig = LangGraphRunnableConfig<
|
export type GraphConfig = LangGraphRunnableConfig<
|
||||||
|
|
|
||||||
182
yarn.lock
182
yarn.lock
|
|
@ -2087,6 +2087,75 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@octokit/app@npm:^16.0.1":
|
||||||
|
version: 16.0.1
|
||||||
|
resolution: "@octokit/app@npm: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
|
||||||
|
checksum: a6c8fb0d1fda7da9022bb04b3e4baf3e30320eff03de63cdf34a0bf90a1afd471705063c909137f308b6bec359c6762e9a0f1303a53853770e06dabaa6f6460d
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@octokit/auth-app@npm:^8.0.1":
|
||||||
|
version: 8.0.1
|
||||||
|
resolution: "@octokit/auth-app@npm: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
|
||||||
|
checksum: ca7ede162bfec0602f3578775f81a5c2f2b8c691ec51ae619fb0943b23952da4842495147333d8938faef01fa1dcc43885df9da320140f774a110ded0042ddec
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@octokit/auth-oauth-app@npm:^9.0.1":
|
||||||
|
version: 9.0.1
|
||||||
|
resolution: "@octokit/auth-oauth-app@npm:9.0.1"
|
||||||
|
dependencies:
|
||||||
|
"@octokit/auth-oauth-device": ^8.0.1
|
||||||
|
"@octokit/auth-oauth-user": ^6.0.0
|
||||||
|
"@octokit/request": ^10.0.2
|
||||||
|
"@octokit/types": ^14.0.0
|
||||||
|
universal-user-agent: ^7.0.0
|
||||||
|
checksum: cf6e6b87cc04638c88b1eda5d43a41fc524262e89ad73d5ad19ac1ae63e35727d867966a2e036bbf9699da9f7256d50b2d735a2e3d2fff84610d44a5107f5a86
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@octokit/auth-oauth-device@npm:^8.0.1":
|
||||||
|
version: 8.0.1
|
||||||
|
resolution: "@octokit/auth-oauth-device@npm:8.0.1"
|
||||||
|
dependencies:
|
||||||
|
"@octokit/oauth-methods": ^6.0.0
|
||||||
|
"@octokit/request": ^10.0.2
|
||||||
|
"@octokit/types": ^14.0.0
|
||||||
|
universal-user-agent: ^7.0.0
|
||||||
|
checksum: 074d1fb0b39efa0f440cb9e2b70bab920920d39d43f05210764a7e1f4c0afac71c7d9c5bfbce62e979d1adf886c6b9788a99a7617098970790e87963125d5264
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@octokit/auth-oauth-user@npm:^6.0.0":
|
||||||
|
version: 6.0.0
|
||||||
|
resolution: "@octokit/auth-oauth-user@npm:6.0.0"
|
||||||
|
dependencies:
|
||||||
|
"@octokit/auth-oauth-device": ^8.0.1
|
||||||
|
"@octokit/oauth-methods": ^6.0.0
|
||||||
|
"@octokit/request": ^10.0.2
|
||||||
|
"@octokit/types": ^14.0.0
|
||||||
|
universal-user-agent: ^7.0.0
|
||||||
|
checksum: 663b856fc3dd7047d08220bef5444d05ba748b1cb34bca48f485fc467283fdac64c3cdf5a21eae503058b4372ae0092d705b3ee59583715393229f9028370bb2
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@octokit/auth-token@npm:^6.0.0":
|
"@octokit/auth-token@npm:^6.0.0":
|
||||||
version: 6.0.0
|
version: 6.0.0
|
||||||
resolution: "@octokit/auth-token@npm:6.0.0"
|
resolution: "@octokit/auth-token@npm:6.0.0"
|
||||||
|
|
@ -2094,6 +2163,16 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@octokit/auth-unauthenticated@npm:^7.0.1":
|
||||||
|
version: 7.0.1
|
||||||
|
resolution: "@octokit/auth-unauthenticated@npm:7.0.1"
|
||||||
|
dependencies:
|
||||||
|
"@octokit/request-error": ^7.0.0
|
||||||
|
"@octokit/types": ^14.0.0
|
||||||
|
checksum: 825eaf2c2df66bd307984098440caac24d50f7e5c3e56bddaf96f6b46814be28b4d550de44786c1c8bcb744a962ade1fd92eecf3a59e422e776adc1344e6268f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@octokit/core@npm:^7.0.2":
|
"@octokit/core@npm:^7.0.2":
|
||||||
version: 7.0.2
|
version: 7.0.2
|
||||||
resolution: "@octokit/core@npm:7.0.2"
|
resolution: "@octokit/core@npm:7.0.2"
|
||||||
|
|
@ -2130,6 +2209,41 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@octokit/oauth-app@npm:^8.0.1":
|
||||||
|
version: 8.0.1
|
||||||
|
resolution: "@octokit/oauth-app@npm:8.0.1"
|
||||||
|
dependencies:
|
||||||
|
"@octokit/auth-oauth-app": ^9.0.1
|
||||||
|
"@octokit/auth-oauth-user": ^6.0.0
|
||||||
|
"@octokit/auth-unauthenticated": ^7.0.1
|
||||||
|
"@octokit/core": ^7.0.2
|
||||||
|
"@octokit/oauth-authorization-url": ^8.0.0
|
||||||
|
"@octokit/oauth-methods": ^6.0.0
|
||||||
|
"@types/aws-lambda": ^8.10.83
|
||||||
|
universal-user-agent: ^7.0.0
|
||||||
|
checksum: ef03e4fe7da34930aa14b9170a0f53d278f398b30e5f2c033bb8836f64d1226989f2bb25456cf6b234ae3ab84e30b6d329706ca0604e0efff1ad81ae5f6b4749
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@octokit/oauth-authorization-url@npm:^8.0.0":
|
||||||
|
version: 8.0.0
|
||||||
|
resolution: "@octokit/oauth-authorization-url@npm:8.0.0"
|
||||||
|
checksum: 8f7431f986ed87e3980b6647c2af7a4df383116e0b4f37a17d10708bb4b7d93dcb2de6f05228539a77f050c0786498f2a62ce71243299d1051e54746484b07f1
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@octokit/oauth-methods@npm:^6.0.0":
|
||||||
|
version: 6.0.0
|
||||||
|
resolution: "@octokit/oauth-methods@npm:6.0.0"
|
||||||
|
dependencies:
|
||||||
|
"@octokit/oauth-authorization-url": ^8.0.0
|
||||||
|
"@octokit/request": ^10.0.2
|
||||||
|
"@octokit/request-error": ^7.0.0
|
||||||
|
"@octokit/types": ^14.0.0
|
||||||
|
checksum: f17ab37869b13bf832aea5471d96d8e5ebe1f14b079cde9d7400afe683e5925ec87f6555c92abf9e825b2013c43c667761472b945cc1405cef89c8c10ab19f52
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@octokit/openapi-types@npm:^20.0.0":
|
"@octokit/openapi-types@npm:^20.0.0":
|
||||||
version: 20.0.0
|
version: 20.0.0
|
||||||
resolution: "@octokit/openapi-types@npm:20.0.0"
|
resolution: "@octokit/openapi-types@npm:20.0.0"
|
||||||
|
|
@ -2144,6 +2258,24 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@octokit/openapi-webhooks-types@npm:12.0.3":
|
||||||
|
version: 12.0.3
|
||||||
|
resolution: "@octokit/openapi-webhooks-types@npm:12.0.3"
|
||||||
|
checksum: 63304f68de7484eef7aa7dfde0a9825b46f7cd99d792560c3a8e48ba740db247ae2bf8a0254ff7018f4b27ff54f29c2d5abfc47cde8aa08cbfcb5abe165a10cd
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@octokit/plugin-paginate-rest@npm:^13.0.0":
|
||||||
|
version: 13.1.0
|
||||||
|
resolution: "@octokit/plugin-paginate-rest@npm:13.1.0"
|
||||||
|
dependencies:
|
||||||
|
"@octokit/types": ^14.1.0
|
||||||
|
peerDependencies:
|
||||||
|
"@octokit/core": ">=6"
|
||||||
|
checksum: e697757714f7c66a9e66737d34619040dd332ded58c6f78ea44917665b3b87a7a8d741465c8bc012b728de517ccf7d3b9382b08be09d7ac6b700a1d2e55b5d83
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@octokit/plugin-paginate-rest@npm:^13.0.1":
|
"@octokit/plugin-paginate-rest@npm:^13.0.1":
|
||||||
version: 13.0.1
|
version: 13.0.1
|
||||||
resolution: "@octokit/plugin-paginate-rest@npm:13.0.1"
|
resolution: "@octokit/plugin-paginate-rest@npm:13.0.1"
|
||||||
|
|
@ -2227,6 +2359,24 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@octokit/webhooks-methods@npm:^6.0.0":
|
||||||
|
version: 6.0.0
|
||||||
|
resolution: "@octokit/webhooks-methods@npm:6.0.0"
|
||||||
|
checksum: 6edf536eb8a695e1c26b7530f14bfaefdeec4e92f4a43e8f27b9a10175c0904f51804801f742d4ea60a4e9b6021dc74c9ef02079a0c979b0e8be5eccabea3c45
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@octokit/webhooks@npm:^14.0.0, @octokit/webhooks@npm:^14.0.2":
|
||||||
|
version: 14.0.2
|
||||||
|
resolution: "@octokit/webhooks@npm:14.0.2"
|
||||||
|
dependencies:
|
||||||
|
"@octokit/openapi-webhooks-types": 12.0.3
|
||||||
|
"@octokit/request-error": ^7.0.0
|
||||||
|
"@octokit/webhooks-methods": ^6.0.0
|
||||||
|
checksum: 129bb615eed93fc1c8f20fe60bb5f9548821b27fd4a5b3fc2d6e81be3fe5163f3b427181c945cefa3196a3b02d18dc642127a4bf68de5ca50e52a79039c4dde7
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@open-draft/deferred-promise@npm:^2.2.0":
|
"@open-draft/deferred-promise@npm:^2.2.0":
|
||||||
version: 2.2.0
|
version: 2.2.0
|
||||||
resolution: "@open-draft/deferred-promise@npm:2.2.0"
|
resolution: "@open-draft/deferred-promise@npm:2.2.0"
|
||||||
|
|
@ -2266,7 +2416,10 @@ __metadata:
|
||||||
"@langchain/langgraph-cli": latest
|
"@langchain/langgraph-cli": latest
|
||||||
"@langchain/langgraph-sdk": ^0.0.85
|
"@langchain/langgraph-sdk": ^0.0.85
|
||||||
"@langchain/openai": ^0.5.10
|
"@langchain/openai": ^0.5.10
|
||||||
|
"@octokit/app": ^16.0.1
|
||||||
|
"@octokit/core": ^7.0.2
|
||||||
"@octokit/rest": ^22.0.0
|
"@octokit/rest": ^22.0.0
|
||||||
|
"@octokit/webhooks": ^14.0.2
|
||||||
"@open-swe/shared": "*"
|
"@open-swe/shared": "*"
|
||||||
"@tsconfig/recommended": ^1.0.8
|
"@tsconfig/recommended": ^1.0.8
|
||||||
"@types/jest": ^29.5.0
|
"@types/jest": ^29.5.0
|
||||||
|
|
@ -2278,6 +2431,7 @@ __metadata:
|
||||||
eslint-plugin-import: ^2.27.5
|
eslint-plugin-import: ^2.27.5
|
||||||
eslint-plugin-no-instanceof: ^1.0.1
|
eslint-plugin-no-instanceof: ^1.0.1
|
||||||
eslint-plugin-prettier: ^4.2.1
|
eslint-plugin-prettier: ^4.2.1
|
||||||
|
hono: ^4.8.3
|
||||||
jest: ^29.7.0
|
jest: ^29.7.0
|
||||||
langchain: ^0.3.26
|
langchain: ^0.3.26
|
||||||
langsmith: ^0.3.29
|
langsmith: ^0.3.29
|
||||||
|
|
@ -3663,6 +3817,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@types/aws-lambda@npm:^8.10.83":
|
||||||
|
version: 8.10.150
|
||||||
|
resolution: "@types/aws-lambda@npm:8.10.150"
|
||||||
|
checksum: 5bde42dfb6f566f00c55542585f81ecf6cda8063b9c27a3a49da6cab853c222987f078315000b0b9d2668a4398507c37378de66d6472727fcf3c1c6360641964
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@types/babel__core@npm:^7.1.14":
|
"@types/babel__core@npm:^7.1.14":
|
||||||
version: 7.20.5
|
version: 7.20.5
|
||||||
resolution: "@types/babel__core@npm:7.20.5"
|
resolution: "@types/babel__core@npm:7.20.5"
|
||||||
|
|
@ -7943,6 +8104,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"hono@npm:^4.8.3":
|
||||||
|
version: 4.8.3
|
||||||
|
resolution: "hono@npm:4.8.3"
|
||||||
|
checksum: cacd43002e5345547cd568c4d831f5d40b54ec8fe2f80dd03cf21d45d2112f31d08e6f57399d7ae08c0fc2329342113cdc4f0e46f3de52c971afe8ef9414c51f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"html-escaper@npm:^2.0.0":
|
"html-escaper@npm:^2.0.0":
|
||||||
version: 2.0.2
|
version: 2.0.2
|
||||||
resolution: "html-escaper@npm:2.0.2"
|
resolution: "html-escaper@npm:2.0.2"
|
||||||
|
|
@ -13391,6 +13559,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"toad-cache@npm:^3.7.0":
|
||||||
|
version: 3.7.0
|
||||||
|
resolution: "toad-cache@npm:3.7.0"
|
||||||
|
checksum: d0f2092ab2c0f3355d3537c41b13888a12996f38080e6c39907e715eb382d997ccf61baab9e8eda3f202b6c07e304728106be3631c9fe3b6c001aaf15b7bdb8f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"toidentifier@npm:1.0.1":
|
"toidentifier@npm:1.0.1":
|
||||||
version: 1.0.1
|
version: 1.0.1
|
||||||
resolution: "toidentifier@npm:1.0.1"
|
resolution: "toidentifier@npm:1.0.1"
|
||||||
|
|
@ -13901,6 +14076,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"universal-github-app-jwt@npm:^2.2.0":
|
||||||
|
version: 2.2.2
|
||||||
|
resolution: "universal-github-app-jwt@npm:2.2.2"
|
||||||
|
checksum: 95c0801e69898aec15b42731b47e7a5a830dbe7e456ba84b66b9b78542d2e79a62d633424c98c58e6fe6b09fbad6951111cce7db85573955c14dd930ecda0ee4
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"universal-user-agent@npm:^7.0.0, universal-user-agent@npm:^7.0.2":
|
"universal-user-agent@npm:^7.0.0, universal-user-agent@npm:^7.0.2":
|
||||||
version: 7.0.3
|
version: 7.0.3
|
||||||
resolution: "universal-user-agent@npm:7.0.3"
|
resolution: "universal-user-agent@npm:7.0.3"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue