mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 05:43:14 +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-sdk": "^0.0.85",
|
||||
"@langchain/openai": "^0.5.10",
|
||||
"@octokit/app": "^16.0.1",
|
||||
"@octokit/core": "^7.0.2",
|
||||
"@octokit/rest": "^22.0.0",
|
||||
"@octokit/webhooks": "^14.0.2",
|
||||
"@open-swe/shared": "*",
|
||||
"diff": "^8.0.1",
|
||||
"hono": "^4.8.3",
|
||||
"langchain": "^0.3.26",
|
||||
"langsmith": "^0.3.29",
|
||||
"uuid": "^11.0.5",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,12 @@
|
|||
import { spawn } from "child_process";
|
||||
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.
|
||||
* This script starts the dev server and monitors the output for 30 seconds
|
||||
|
|
@ -18,6 +24,7 @@ function checkDevServer(): Promise<void> {
|
|||
cwd: targetCwd,
|
||||
shell: true,
|
||||
stdio: "pipe",
|
||||
env: REQUIRED_ENV,
|
||||
});
|
||||
|
||||
let errorDetected = false;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import { createLangGraphClient } from "../../../utils/langgraph-client.js";
|
|||
import {
|
||||
GITHUB_INSTALLATION_TOKEN_COOKIE,
|
||||
GITHUB_TOKEN_COOKIE,
|
||||
GITHUB_USER_ID_HEADER,
|
||||
GITHUB_USER_LOGIN_HEADER,
|
||||
} from "@open-swe/shared/constants";
|
||||
import {
|
||||
BaseMessage,
|
||||
|
|
@ -221,6 +223,10 @@ export async function classifyMessage(
|
|||
[GITHUB_TOKEN_COOKIE]: config.configurable?.[GITHUB_TOKEN_COOKIE] ?? "",
|
||||
[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 {
|
||||
GITHUB_INSTALLATION_TOKEN_COOKIE,
|
||||
GITHUB_TOKEN_COOKIE,
|
||||
GITHUB_USER_ID_HEADER,
|
||||
GITHUB_USER_LOGIN_HEADER,
|
||||
} from "@open-swe/shared/constants";
|
||||
import { createLangGraphClient } from "../../../utils/langgraph-client.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_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 {
|
||||
GITHUB_INSTALLATION_TOKEN_COOKIE,
|
||||
GITHUB_TOKEN_COOKIE,
|
||||
GITHUB_USER_ID_HEADER,
|
||||
GITHUB_USER_LOGIN_HEADER,
|
||||
} from "@open-swe/shared/constants";
|
||||
import { createLogger, LogLevel } from "../../../utils/logger.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_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 {
|
||||
GITHUB_INSTALLATION_TOKEN_COOKIE,
|
||||
GITHUB_TOKEN_COOKIE,
|
||||
GITHUB_USER_ID_HEADER,
|
||||
GITHUB_USER_LOGIN_HEADER,
|
||||
PLAN_INTERRUPT_ACTION_TITLE,
|
||||
PLAN_INTERRUPT_DELIMITER,
|
||||
} from "@open-swe/shared/constants";
|
||||
|
|
@ -70,6 +72,10 @@ export async function interruptProposedPlan(
|
|||
[GITHUB_TOKEN_COOKIE]: config.configurable?.[GITHUB_TOKEN_COOKIE] ?? "",
|
||||
[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 {
|
||||
verifyGithubUser,
|
||||
GithubUser,
|
||||
verifyGithubUserId,
|
||||
} from "@open-swe/shared/github/verify-user";
|
||||
import {
|
||||
GITHUB_INSTALLATION_TOKEN_COOKIE,
|
||||
GITHUB_TOKEN_COOKIE,
|
||||
GITHUB_USER_ID_HEADER,
|
||||
GITHUB_USER_LOGIN_HEADER,
|
||||
} from "@open-swe/shared/constants";
|
||||
import { decryptGitHubToken } from "@open-swe/shared/crypto";
|
||||
|
||||
const STUDIO_USER_ID = "langgraph-studio-user";
|
||||
|
||||
// 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 };
|
||||
};
|
||||
import { verifyGitHubWebhookOrThrow } from "./github.js";
|
||||
import { createWithOwnerMetadata, createOwnerFilter } from "./utils.js";
|
||||
|
||||
export const auth = new Auth()
|
||||
.authenticate(async (request: Request) => {
|
||||
|
|
@ -45,6 +24,13 @@ export const auth = new Auth()
|
|||
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;
|
||||
if (!encryptionKey) {
|
||||
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
|
||||
// exists as it will cause issues later on if it's not present.
|
||||
const encryptedInstallationToken = request.headers.get(
|
||||
|
|
@ -70,24 +49,37 @@ export const auth = new Auth()
|
|||
});
|
||||
}
|
||||
|
||||
// Validate GitHub access token
|
||||
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.
|
||||
user = await verifyGithubUser(
|
||||
decryptGitHubToken(encryptedAccessToken, encryptionKey),
|
||||
);
|
||||
if (!user) {
|
||||
throw new HTTPException(401, {
|
||||
message:
|
||||
"Invalid GitHub token or user is not a member of the required organization.",
|
||||
});
|
||||
}
|
||||
} catch (e: any) {
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
throw new HTTPException(401, {
|
||||
message: `Authentication error: ${e.message}`,
|
||||
message: "User not found",
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
identity: user.id.toString(),
|
||||
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 encryptedInstallationToken =
|
||||
config.configurable[GITHUB_INSTALLATION_TOKEN_COOKIE];
|
||||
if (!encryptedGitHubToken || !encryptedInstallationToken) {
|
||||
if (!encryptedInstallationToken) {
|
||||
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
|
||||
const githubAccessToken = decryptGitHubToken(
|
||||
encryptedGitHubToken,
|
||||
encryptionKey,
|
||||
);
|
||||
const githubAccessToken = encryptedGitHubToken
|
||||
? decryptGitHubToken(encryptedGitHubToken, encryptionKey)
|
||||
: "";
|
||||
const githubInstallationToken = decryptGitHubToken(
|
||||
encryptedInstallationToken,
|
||||
encryptionKey,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Octokit } from "@octokit/rest";
|
||||
import { createLogger, LogLevel } from "../logger.js";
|
||||
import { GitHubIssue, GitHubIssueComment, GitHubPullRequest } from "./types.js";
|
||||
import { getOpenSWELabel } from "./label.js";
|
||||
|
||||
const logger = createLogger(LogLevel.INFO, "GitHub-API");
|
||||
|
||||
|
|
@ -141,7 +142,7 @@ export async function createPullRequest({
|
|||
owner,
|
||||
repo,
|
||||
issue_number: pullRequest.number,
|
||||
labels: ["open-swe"],
|
||||
labels: [getOpenSWELabel()],
|
||||
});
|
||||
logger.info("Added 'open-swe' label to pull request", {
|
||||
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 { useThreads } from "@/hooks/useThreads";
|
||||
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 { GraphState } from "@open-swe/shared/open-swe/types";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
|
@ -24,14 +25,12 @@ export default function ThreadPage({
|
|||
const { thread_id } = use(params);
|
||||
const stream = useStream<ManagerGraphState>({
|
||||
apiUrl: process.env.NEXT_PUBLIC_API_URL ?? "",
|
||||
assistantId: process.env.NEXT_PUBLIC_MANAGER_ASSISTANT_ID ?? "",
|
||||
assistantId: MANAGER_GRAPH_ID,
|
||||
threadId: thread_id,
|
||||
reconnectOnMount: true,
|
||||
});
|
||||
|
||||
const { threads, threadsLoading } = useThreads<GraphState>(
|
||||
process.env.NEXT_PUBLIC_MANAGER_ASSISTANT_ID,
|
||||
);
|
||||
const { threads, threadsLoading } = useThreads<GraphState>(MANAGER_GRAPH_ID);
|
||||
// Find the thread by 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 { Toaster } from "@/components/ui/sonner";
|
||||
import { Suspense } from "react";
|
||||
import { MANAGER_GRAPH_ID } from "@open-swe/shared/constants";
|
||||
|
||||
export default function ChatPage() {
|
||||
const { threads, threadsLoading } = useThreads<GraphState>(
|
||||
process.env.NEXT_PUBLIC_MANAGER_ASSISTANT_ID,
|
||||
);
|
||||
const { threads, threadsLoading } = useThreads<GraphState>(MANAGER_GRAPH_ID);
|
||||
|
||||
// Convert Thread objects to ThreadDisplayInfo for UI
|
||||
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 { ThreadCard, ThreadCardLoading } from "@/components/v2/thread-card";
|
||||
import { ThemeToggle } from "@/components/theme-toggle";
|
||||
import { MANAGER_GRAPH_ID } from "@open-swe/shared/constants";
|
||||
|
||||
type FilterStatus = "all" | "running" | "completed" | "failed" | "pending";
|
||||
|
||||
export default function AllThreadsPage() {
|
||||
const router = useRouter();
|
||||
const { threads, threadsLoading } = useThreads<GraphState>(
|
||||
process.env.NEXT_PUBLIC_MANAGER_ASSISTANT_ID,
|
||||
);
|
||||
const { threads, threadsLoading } = useThreads<GraphState>(MANAGER_GRAPH_ID);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [statusFilter, setStatusFilter] = useState<FilterStatus>("all");
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import { GitHubInstallationBanner } from "../github/installation-banner";
|
|||
import { QuickActions } from "./quick-actions";
|
||||
import { useState } from "react";
|
||||
import { GitHubLogoutButton } from "../github/github-oauth-button";
|
||||
import { MANAGER_GRAPH_ID } from "@open-swe/shared/constants";
|
||||
|
||||
interface DefaultViewProps {
|
||||
threads: ThreadDisplayInfo[];
|
||||
|
|
@ -37,8 +38,7 @@ export function DefaultView({ threads, threadsLoading }: DefaultViewProps) {
|
|||
const router = useRouter();
|
||||
const [quickActionPrompt, setQuickActionPrompt] = useState("");
|
||||
const apiUrl: string | undefined = process.env.NEXT_PUBLIC_API_URL ?? "";
|
||||
const assistantId: string | undefined =
|
||||
process.env.NEXT_PUBLIC_MANAGER_ASSISTANT_ID ?? "";
|
||||
const assistantId: string | undefined = MANAGER_GRAPH_ID;
|
||||
const {
|
||||
contentBlocks,
|
||||
setContentBlocks,
|
||||
|
|
@ -49,8 +49,8 @@ export function DefaultView({ threads, threadsLoading }: DefaultViewProps) {
|
|||
handlePaste,
|
||||
} = useFileUpload();
|
||||
|
||||
if (!apiUrl || !assistantId) {
|
||||
return <div>Missing API URL or Assistant ID</div>;
|
||||
if (!apiUrl) {
|
||||
return <div>Missing API URL environment variable</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { GraphState } from "@open-swe/shared/open-swe/types";
|
|||
import { Base64ContentBlock, HumanMessage } from "@langchain/core/messages";
|
||||
import { toast } from "sonner";
|
||||
import { DEFAULT_CONFIG_KEY, useConfigStore } from "@/hooks/useConfigStore";
|
||||
import { MANAGER_GRAPH_ID } from "@open-swe/shared/constants";
|
||||
|
||||
interface TerminalInputProps {
|
||||
placeholder?: string;
|
||||
|
|
@ -51,14 +52,7 @@ export function TerminalInput({
|
|||
});
|
||||
|
||||
const handleSend = async () => {
|
||||
const assistantId = process.env.NEXT_PUBLIC_MANAGER_ASSISTANT_ID;
|
||||
if (!assistantId) {
|
||||
toast.error("No assistant ID found", {
|
||||
richColors: true,
|
||||
closeButton: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const assistantId = MANAGER_GRAPH_ID;
|
||||
if (!selectedRepository) {
|
||||
toast.error("Please select a repository first", {
|
||||
richColors: true,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,11 @@ import { GraphState } from "@open-swe/shared/open-swe/types";
|
|||
import { ActionsRenderer } from "./actions-renderer";
|
||||
import { ThemeToggle } from "../theme-toggle";
|
||||
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 {
|
||||
StickyToBottomContent,
|
||||
|
|
@ -23,9 +27,6 @@ import {
|
|||
} from "../../utils/scroll-utils";
|
||||
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 {
|
||||
stream: ReturnType<typeof useStream<ManagerGraphState>>;
|
||||
displayThread: ThreadDisplayInfo;
|
||||
|
|
@ -153,23 +154,17 @@ export function ThreadView({
|
|||
<TabsContent value="planner">
|
||||
<Card className="border-border bg-card px-0 py-4 dark:bg-gray-950">
|
||||
<CardContent className="space-y-2 p-3 pt-0">
|
||||
{plannerThreadId &&
|
||||
plannerRunId &&
|
||||
PLANNER_ASSISTANT_ID && (
|
||||
<ActionsRenderer<PlannerGraphState>
|
||||
graphId={PLANNER_ASSISTANT_ID}
|
||||
threadId={plannerThreadId}
|
||||
runId={plannerRunId}
|
||||
setProgrammerSession={setProgrammerSession}
|
||||
programmerSession={programmerSession}
|
||||
setSelectedTab={setSelectedTab}
|
||||
/>
|
||||
)}
|
||||
{!(
|
||||
plannerThreadId &&
|
||||
plannerRunId &&
|
||||
PLANNER_ASSISTANT_ID
|
||||
) && (
|
||||
{plannerThreadId && plannerRunId && (
|
||||
<ActionsRenderer<PlannerGraphState>
|
||||
graphId={PLANNER_GRAPH_ID}
|
||||
threadId={plannerThreadId}
|
||||
runId={plannerRunId}
|
||||
setProgrammerSession={setProgrammerSession}
|
||||
programmerSession={programmerSession}
|
||||
setSelectedTab={setSelectedTab}
|
||||
/>
|
||||
)}
|
||||
{!(plannerThreadId && plannerRunId) && (
|
||||
<div className="flex items-center justify-center gap-2 py-8">
|
||||
<Clock className="text-muted-foreground size-4" />
|
||||
<span className="text-muted-foreground text-sm">
|
||||
|
|
@ -183,9 +178,9 @@ export function ThreadView({
|
|||
<TabsContent value="programmer">
|
||||
<Card className="border-border bg-card px-0 py-4 dark:bg-gray-950">
|
||||
<CardContent className="space-y-2 p-3 pt-0">
|
||||
{programmerSession && PROGRAMMER_ASSISTANT_ID && (
|
||||
{programmerSession && (
|
||||
<ActionsRenderer<GraphState>
|
||||
graphId={PROGRAMMER_ASSISTANT_ID}
|
||||
graphId={PROGRAMMER_GRAPH_ID}
|
||||
threadId={programmerSession.threadId}
|
||||
runId={programmerSession.runId}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import {
|
|||
CustomNodeEvent,
|
||||
isCustomNodeEvent,
|
||||
} from "@open-swe/shared/open-swe/custom-node-events";
|
||||
import { MANAGER_GRAPH_ID } from "@open-swe/shared/constants";
|
||||
|
||||
const useTypedStream = useStream<
|
||||
GraphState,
|
||||
|
|
@ -37,18 +38,15 @@ const StreamSession = ({ children }: { children: ReactNode }) => {
|
|||
const [customEvents, setCustomEvents] = useState<CustomNodeEvent[]>([]);
|
||||
const { refreshThreads } = useThreadsContext();
|
||||
|
||||
if (
|
||||
!process.env.NEXT_PUBLIC_API_URL ||
|
||||
!process.env.NEXT_PUBLIC_MANAGER_ASSISTANT_ID
|
||||
) {
|
||||
if (!process.env.NEXT_PUBLIC_API_URL) {
|
||||
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({
|
||||
apiUrl: process.env.NEXT_PUBLIC_API_URL,
|
||||
assistantId: process.env.NEXT_PUBLIC_MANAGER_ASSISTANT_ID,
|
||||
assistantId: MANAGER_GRAPH_ID,
|
||||
reconnectOnMount: true,
|
||||
threadId: threadId ?? null,
|
||||
onCustomEvent: (event, options) => {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@
|
|||
"dependencies": ["./apps/open-swe"],
|
||||
"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_INSTALLATION_ID_COOKIE = "github_installation_id";
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
GITHUB_INSTALLATION_TOKEN_COOKIE,
|
||||
GITHUB_TOKEN_COOKIE,
|
||||
GITHUB_USER_ID_HEADER,
|
||||
GITHUB_USER_LOGIN_HEADER,
|
||||
} from "../constants.js";
|
||||
import { withLangGraph } from "@langchain/langgraph/zod";
|
||||
import { BaseMessage } from "@langchain/core/messages";
|
||||
|
|
@ -395,6 +397,16 @@ export const GraphConfigurationMetadata: {
|
|||
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({
|
||||
|
|
@ -550,6 +562,20 @@ export const GraphConfiguration = z.object({
|
|||
.langgraph.metadata(
|
||||
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<
|
||||
|
|
|
|||
182
yarn.lock
182
yarn.lock
|
|
@ -2087,6 +2087,75 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 6.0.0
|
||||
resolution: "@octokit/auth-token@npm:6.0.0"
|
||||
|
|
@ -2094,6 +2163,16 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 7.0.2
|
||||
resolution: "@octokit/core@npm:7.0.2"
|
||||
|
|
@ -2130,6 +2209,41 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 20.0.0
|
||||
resolution: "@octokit/openapi-types@npm:20.0.0"
|
||||
|
|
@ -2144,6 +2258,24 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 13.0.1
|
||||
resolution: "@octokit/plugin-paginate-rest@npm:13.0.1"
|
||||
|
|
@ -2227,6 +2359,24 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 2.2.0
|
||||
resolution: "@open-draft/deferred-promise@npm:2.2.0"
|
||||
|
|
@ -2266,7 +2416,10 @@ __metadata:
|
|||
"@langchain/langgraph-cli": latest
|
||||
"@langchain/langgraph-sdk": ^0.0.85
|
||||
"@langchain/openai": ^0.5.10
|
||||
"@octokit/app": ^16.0.1
|
||||
"@octokit/core": ^7.0.2
|
||||
"@octokit/rest": ^22.0.0
|
||||
"@octokit/webhooks": ^14.0.2
|
||||
"@open-swe/shared": "*"
|
||||
"@tsconfig/recommended": ^1.0.8
|
||||
"@types/jest": ^29.5.0
|
||||
|
|
@ -2278,6 +2431,7 @@ __metadata:
|
|||
eslint-plugin-import: ^2.27.5
|
||||
eslint-plugin-no-instanceof: ^1.0.1
|
||||
eslint-plugin-prettier: ^4.2.1
|
||||
hono: ^4.8.3
|
||||
jest: ^29.7.0
|
||||
langchain: ^0.3.26
|
||||
langsmith: ^0.3.29
|
||||
|
|
@ -3663,6 +3817,13 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 7.20.5
|
||||
resolution: "@types/babel__core@npm:7.20.5"
|
||||
|
|
@ -7943,6 +8104,13 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 2.0.2
|
||||
resolution: "html-escaper@npm:2.0.2"
|
||||
|
|
@ -13391,6 +13559,13 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 1.0.1
|
||||
resolution: "toidentifier@npm:1.0.1"
|
||||
|
|
@ -13901,6 +14076,13 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 7.0.3
|
||||
resolution: "universal-user-agent@npm:7.0.3"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue