Show the selected bot profile and add OpenRouter fallback (#18)
Uses Codex first, then Claude, then OpenCode through OpenRouter when quota is exhausted. Every generated comment and review now identifies the selected profile and model in a final quote. The dependent action interface remains one master key. Verified formatting, lint, type checks, encrypted profile decryption, current OpenCode CLI flags, and 45 tests. Reviewed-on: #18 Reviewed-by: bot <noreply@capsulizers.com> Co-authored-by: Danny Kim <temeddix@gmail.com> Co-committed-by: Danny Kim <temeddix@gmail.com>
This commit was merged in pull request #18.
This commit is contained in:
+56
-8
@@ -17,7 +17,13 @@ import {
|
||||
agentEnvironment,
|
||||
runProfiles,
|
||||
sanitizedEnvironment,
|
||||
withProfile,
|
||||
} from "./profiles.ts";
|
||||
import {
|
||||
collectOpenCodeOutput,
|
||||
openCodeArgs,
|
||||
parseOpenCodeEvents,
|
||||
} from "./opencode.ts";
|
||||
import {
|
||||
readRuntimeManifest,
|
||||
type RuntimeOutcome,
|
||||
@@ -30,6 +36,7 @@ type AttemptResult = {
|
||||
body: string;
|
||||
reviewPath: string;
|
||||
anchorsPath: string;
|
||||
profile: Pick<RuntimeProfile, "type" | "model">;
|
||||
};
|
||||
|
||||
const env = (name: string): string => {
|
||||
@@ -109,7 +116,6 @@ async function runAgentCommand(
|
||||
|
||||
async function prepareProfile(
|
||||
profile: RuntimeProfile,
|
||||
token: string | undefined,
|
||||
): Promise<void> {
|
||||
await runAgentCommand(profile, "git", [
|
||||
"config",
|
||||
@@ -117,15 +123,15 @@ async function prepareProfile(
|
||||
"--add",
|
||||
"safe.directory",
|
||||
WORKSPACE,
|
||||
], token);
|
||||
]);
|
||||
await runAgentCommand(profile, "npx", [
|
||||
"-y",
|
||||
...SKILLS_INSTALL.split(" "),
|
||||
"--global",
|
||||
"--agent",
|
||||
profile.type === "claude" ? "claude-code" : "codex",
|
||||
profile.type === "claude" ? "claude-code" : profile.type,
|
||||
"--yes",
|
||||
], token);
|
||||
]);
|
||||
}
|
||||
|
||||
async function renderPrompt(
|
||||
@@ -253,7 +259,7 @@ async function codexDeviceLogin(
|
||||
]);
|
||||
if (exited) throw new Error("AI bot login produced no instructions");
|
||||
}
|
||||
await postAuthorComment(shown);
|
||||
await postAuthorComment(withProfile(shown, profile));
|
||||
const [result] = await Promise.all([status, drained]);
|
||||
if (!result.success) {
|
||||
throw new Error(`codex login exited with ${result.code}`);
|
||||
@@ -357,6 +363,41 @@ async function runCodex(
|
||||
return outputPath;
|
||||
}
|
||||
|
||||
async function runOpenCode(
|
||||
profile: RuntimeProfile,
|
||||
token: string,
|
||||
prompt: string,
|
||||
): Promise<string> {
|
||||
const process = agentCommand(
|
||||
profile,
|
||||
"opencode",
|
||||
openCodeArgs(profile.model, prompt),
|
||||
{
|
||||
cwd: WORKSPACE,
|
||||
env: agentEnvironment(PROCESS_ENV, profile, token),
|
||||
clearEnv: true,
|
||||
stdout: "piped",
|
||||
stderr: "piped",
|
||||
},
|
||||
).spawn();
|
||||
const output = collectOpenCodeOutput(
|
||||
process.stdout,
|
||||
process.stderr,
|
||||
async (chunk) => {
|
||||
await Deno.stdout.write(chunk);
|
||||
},
|
||||
async (chunk) => {
|
||||
await Deno.stderr.write(chunk);
|
||||
},
|
||||
);
|
||||
const [status, streams] = await Promise.all([process.status, output]);
|
||||
const error = streams.stderr.trim();
|
||||
if (!status.success) {
|
||||
throw new Error(error || `opencode exited with ${status.code}`);
|
||||
}
|
||||
return parseOpenCodeEvents(streams.stdout);
|
||||
}
|
||||
|
||||
async function runProfile(profile: RuntimeProfile): Promise<AttemptResult> {
|
||||
const token = await readProfileToken(profile);
|
||||
const authentication = profile.type === "codex"
|
||||
@@ -376,14 +417,16 @@ async function runProfile(profile: RuntimeProfile): Promise<AttemptResult> {
|
||||
profile.gid,
|
||||
);
|
||||
}
|
||||
await prepareProfile(profile, token);
|
||||
await prepareProfile(profile);
|
||||
const rendered = await renderPrompt(profile);
|
||||
reviewPath = rendered.reviewPath;
|
||||
anchorsPath = rendered.anchorsPath;
|
||||
if (profile.type === "claude") {
|
||||
body = await runClaude(profile, token ?? "", rendered.prompt);
|
||||
} else {
|
||||
} else if (profile.type === "codex") {
|
||||
resultPath = await runCodex(profile, rendered.prompt, authentication!);
|
||||
} else {
|
||||
body = await runOpenCode(profile, token ?? "", rendered.prompt);
|
||||
}
|
||||
} finally {
|
||||
await killProfileProcesses(profile.uid);
|
||||
@@ -397,7 +440,12 @@ async function runProfile(profile: RuntimeProfile): Promise<AttemptResult> {
|
||||
) {
|
||||
throw new Error("agent attempt produced no result");
|
||||
}
|
||||
return { body, reviewPath, anchorsPath };
|
||||
return {
|
||||
body,
|
||||
reviewPath,
|
||||
anchorsPath,
|
||||
profile: { type: profile.type, model: profile.model },
|
||||
};
|
||||
}
|
||||
|
||||
async function writeOutcome(outcome: RuntimeOutcome): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user