open-swe/apps/cli/src/utils.ts

122 lines
3.6 KiB
TypeScript
Raw Normal View History

feat: add updates to cli application (#503) * open swe cli: initial cli updates * openswe cli: add working authentication * open swe cli: remove unecessary console log statements * openswe cli: typescript modifications * openswe cli: updates to formats * openswe cli: updates to formatting * openswe cli: ran prettier * openswe cli: update urls * openswe cli: update eslint * openswe cli: add env example * openswe cli: add updates * openswe cli: formatting * openswe cli: add app installation * openswe cli: clean up lint and formatting * temp logs * l * working human feedback prompting * stop the re-renders * NOT WORKING -- KICKOFF PROGRAMMER * approve programmer * detected programmer working * some sembleance of logging * some changes * working logs * concatenated tool results * ui improvements * update ui * formatting * simple interrupt working * working multiple sessions + padding * working dissapearance of the approve/deny * TEST PUSH * cr * generally working tool calls * access thread ip properly * fix naming of env example * add readme * formatting * linting + formatting * dist updates * updates to yarn * no tests for cli * updates to readme * format * update readme * remove auth print out * add loading indicators * formatting changes * remove shared workspace * add lock * add dependency * update yarn lock * add back the installation token * replace with variable constants * Update apps/cli/src/auth-server.ts Co-authored-by: Brace Sproul <braceasproul@gmail.com> * openswe cli: updates * openswe cli: cleanup lint * openswe cli: update stream mode * openswe cli: update stream mode * openswe cli: streaming service * openswe cli: create selector for approval * openswe cli: remove streaming service * openswe cli: update types to langchain * openswe cli: update all streamModes * openswe cli: update formatting * openswe cli: update stream mode * openswe cli: notify of in development * openswe cli: change to istoolmessage --------- Co-authored-by: bracesproul <braceasproul@gmail.com>
2025-07-29 17:37:13 -04:00
/**
* Utility functions for CLI app
*/
import { Client, StreamMode } from "@langchain/langgraph-sdk";
import { encryptSecret } from "@open-swe/shared/crypto";
import {
OPEN_SWE_STREAM_MODE,
PLANNER_GRAPH_ID,
} from "@open-swe/shared/constants";
import {
getAccessToken,
getInstallationAccessToken,
getInstallationId,
} from "./auth-server.js";
import { formatDisplayLog } from "./logger.js";
const LANGGRAPH_URL = process.env.LANGGRAPH_URL || "http://localhost:2024";
/**
* Submit feedback to the planner
*/
export async function submitFeedback({
plannerFeedback,
plannerThreadId,
selectedRepo,
setLogs,
setPlannerFeedback,
}: {
plannerFeedback: string;
plannerThreadId: string;
selectedRepo: any;
setLogs: (updater: (prev: string[]) => string[]) => void; // eslint-disable-line no-unused-vars
setPlannerFeedback: () => void;
}) {
try {
const userAccessToken = getAccessToken();
const installationAccessToken = await getInstallationAccessToken();
const encryptionKey = process.env.SECRETS_ENCRYPTION_KEY;
if (!userAccessToken || !installationAccessToken || !encryptionKey) {
setLogs((prev) => [
...prev,
"Missing access tokens for feedback submission",
]);
return;
}
const encryptedUserToken = encryptSecret(userAccessToken, encryptionKey);
const encryptedInstallationToken = encryptSecret(
installationAccessToken,
encryptionKey,
);
const [owner] = selectedRepo?.full_name.split("/") || [];
const installationId = getInstallationId();
const client = new Client({
apiUrl: LANGGRAPH_URL,
defaultHeaders: {
"x-github-access-token": encryptedUserToken,
"x-github-installation-token": encryptedInstallationToken,
"x-github-installation-name": owner,
"x-github-installation-id": installationId,
},
});
const formatted = formatDisplayLog(`Human feedback: ${plannerFeedback}`);
if (formatted.length > 0) {
setLogs((prev) => [...prev, ...formatted]);
}
// Create a new stream with the feedback
const stream = await client.runs.stream(plannerThreadId, PLANNER_GRAPH_ID, {
command: {
resume: [
{
type: plannerFeedback === "approve" ? "accept" : "ignore",
args: null,
},
],
},
streamMode: OPEN_SWE_STREAM_MODE as StreamMode[],
});
let programmerStreamed = false;
// Process the stream response
for await (const chunk of stream) {
const formatted = formatDisplayLog(chunk);
if (formatted.length > 0) {
setLogs((prev) => [...prev, ...formatted]);
}
// Check for programmer session in the resumed planner stream
const chunkData = chunk.data as any;
if (
!programmerStreamed &&
chunkData?.programmerSession?.threadId &&
typeof chunkData.programmerSession.threadId === "string" &&
typeof chunkData.programmerSession.runId === "string"
) {
programmerStreamed = true;
// Join programmer stream
for await (const programmerChunk of client.runs.joinStream(
chunkData.programmerSession.threadId,
chunkData.programmerSession.runId,
)) {
const formatted = formatDisplayLog(programmerChunk);
if (formatted.length > 0) {
setLogs((prev) => [...prev, ...formatted]);
}
}
}
}
} catch (error: unknown) {
const errorMessage =
error instanceof Error ? error.message : "Unknown error";
setLogs((prev) => [...prev, `Error submitting feedback: ${errorMessage}`]);
} finally {
// Clear feedback state
setPlannerFeedback();
}
}