fix(opencode): add effort-aware model overrides and OpenRouter guidance (#354)
* chore: create empty commit for PR Co-authored-by: Cursor <cursoragent@cursor.com> * chore(action): trigger preview repo creation workflow Co-authored-by: Cursor <cursoragent@cursor.com> * fix(opencode): merge repo opencode config and log provider key presence Co-authored-by: Cursor <cursoragent@cursor.com> * fix(opencode): add effort-aware model override precedence Add OPENCODE_MODEL_MINI and OPENCODE_MODEL_MAX support with fallback to OPENCODE_MODEL and auto-selection, and keep provider resolution aligned via inline OpenCode config when overrides are used. Update workflow env wiring, test allowlists, and docs (including OpenRouter setup guidance and sidebar ordering) to document the new behavior. Co-authored-by: Cursor <cursoragent@cursor.com> * docs(effort): clarify OpenCode precedence and add section link Add a concise OpenCode precedence callout in the summary area and link directly to the OpenCode section for full details and examples. Co-authored-by: Cursor <cursoragent@cursor.com> * Restructure dash (#372) * Restructure dash * WIP * WIP * refactor trigger UI: extract PR summary card, add mentions section, rename labels Co-authored-by: Cursor <cursoragent@cursor.com> * clean up console UI: remove info icons from section descriptions, rename mentions trigger Co-authored-by: Cursor <cursoragent@cursor.com> * fix review feedback: layout, terminology, form scope - extract console sidebar sections to module-level constant - align three-column layout breakpoints to xl (match sidebar visibility) - fix mixed shell/bash terminology in beta page - scope FormProvider to trigger sections only, restore autoComplete="off" Co-authored-by: Cursor <cursoragent@cursor.com> * Bump --------- Co-authored-by: Cursor <cursoragent@cursor.com> * chore: create empty commit for PR Co-authored-by: Cursor <cursoragent@cursor.com> * fix(opencode): merge repo opencode config and log provider key presence Co-authored-by: Cursor <cursoragent@cursor.com> * fix(opencode): add effort-aware model override precedence Add OPENCODE_MODEL_MINI and OPENCODE_MODEL_MAX support with fallback to OPENCODE_MODEL and auto-selection, and keep provider resolution aligned via inline OpenCode config when overrides are used. Update workflow env wiring, test allowlists, and docs (including OpenRouter setup guidance and sidebar ordering) to document the new behavior. Co-authored-by: Cursor <cursoragent@cursor.com> * chore: update agent rules and OpenCode config docs Align AGENTS guidance with current preferences and apply review-driven wording updates in OpenCode-related files. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
committed by
pullfrog[bot]
parent
e2d8dfeebf
commit
b472aa1ba9
@@ -144201,7 +144201,7 @@ function initToolState(params) {
|
||||
return {
|
||||
progressCommentId: resolvedId,
|
||||
subagents: /* @__PURE__ */ new Map(),
|
||||
selfSubagentId: void 0,
|
||||
activeSubagentId: void 0,
|
||||
backgroundProcesses: /* @__PURE__ */ new Map(),
|
||||
usageEntries: []
|
||||
};
|
||||
@@ -144238,43 +144238,26 @@ function isAddressInUse(error49) {
|
||||
const message = getErrorMessage(error49).toLowerCase();
|
||||
return message.includes("eaddrinuse") || message.includes("address already in use");
|
||||
}
|
||||
function buildSubagentTools(ctx) {
|
||||
const tools = [
|
||||
IssueInfoTool(ctx),
|
||||
GetIssueCommentsTool(ctx),
|
||||
GetIssueEventsTool(ctx),
|
||||
PullRequestInfoTool(ctx),
|
||||
CommitInfoTool(ctx),
|
||||
GetReviewCommentsTool(ctx),
|
||||
ListPullRequestReviewsTool(ctx),
|
||||
GetCheckSuiteLogsTool(ctx),
|
||||
UploadFileTool(ctx),
|
||||
SetOutputTool(ctx),
|
||||
FileReadTool(ctx),
|
||||
FileWriteTool(ctx),
|
||||
FileEditTool(ctx),
|
||||
FileDeleteTool(ctx),
|
||||
ListDirectoryTool(ctx)
|
||||
];
|
||||
if (ctx.payload.shell === "restricted") {
|
||||
tools.push(ShellTool(ctx));
|
||||
tools.push(KillBackgroundTool(ctx));
|
||||
}
|
||||
return tools;
|
||||
}
|
||||
function buildOrchestratorTools(ctx) {
|
||||
function buildCommonTools(ctx) {
|
||||
const tools = [
|
||||
StartDependencyInstallationTool(ctx),
|
||||
AwaitDependencyInstallationTool(ctx),
|
||||
CreateCommentTool(ctx),
|
||||
EditCommentTool(ctx),
|
||||
ReplyToReviewCommentTool(ctx),
|
||||
IssueTool(ctx),
|
||||
IssueInfoTool(ctx),
|
||||
GetIssueCommentsTool(ctx),
|
||||
GetIssueEventsTool(ctx),
|
||||
CreatePullRequestReviewTool(ctx),
|
||||
PullRequestInfoTool(ctx),
|
||||
CommitInfoTool(ctx),
|
||||
CheckoutPrTool(ctx),
|
||||
GetReviewCommentsTool(ctx),
|
||||
ListPullRequestReviewsTool(ctx),
|
||||
ResolveReviewThreadTool(ctx),
|
||||
GetCheckSuiteLogsTool(ctx),
|
||||
AddLabelsTool(ctx),
|
||||
GitTool(ctx),
|
||||
GitFetchTool(ctx),
|
||||
UploadFileTool(ctx),
|
||||
@@ -144284,14 +144267,17 @@ function buildOrchestratorTools(ctx) {
|
||||
FileEditTool(ctx),
|
||||
FileDeleteTool(ctx),
|
||||
ListDirectoryTool(ctx),
|
||||
CreateCommentTool(ctx),
|
||||
EditCommentTool(ctx),
|
||||
ReplyToReviewCommentTool(ctx),
|
||||
CreatePullRequestReviewTool(ctx),
|
||||
ResolveReviewThreadTool(ctx),
|
||||
IssueTool(ctx),
|
||||
AddLabelsTool(ctx),
|
||||
ReportProgressTool(ctx),
|
||||
ReportProgressTool(ctx)
|
||||
];
|
||||
if (ctx.payload.shell === "restricted") {
|
||||
tools.push(ShellTool(ctx));
|
||||
tools.push(KillBackgroundTool(ctx));
|
||||
}
|
||||
return tools;
|
||||
}
|
||||
function buildOrchestratorTools(ctx) {
|
||||
return [
|
||||
...buildCommonTools(ctx),
|
||||
SelectModeTool(ctx),
|
||||
DelegateTool(ctx),
|
||||
AskQuestionTool(ctx),
|
||||
@@ -144301,11 +144287,9 @@ function buildOrchestratorTools(ctx) {
|
||||
CreatePullRequestTool(ctx),
|
||||
UpdatePullRequestBodyTool(ctx)
|
||||
];
|
||||
if (ctx.payload.shell === "restricted") {
|
||||
tools.push(ShellTool(ctx));
|
||||
tools.push(KillBackgroundTool(ctx));
|
||||
}
|
||||
return tools;
|
||||
}
|
||||
function buildSubagentTools(ctx) {
|
||||
return buildCommonTools(ctx);
|
||||
}
|
||||
async function tryStartMcpServer(ctx, tools, port) {
|
||||
const server = new FastMCP({ name: ghPullfrogMcpName, version: "0.0.1" });
|
||||
@@ -144395,13 +144379,12 @@ async function startMcpHttpServer(ctx) {
|
||||
}
|
||||
};
|
||||
}
|
||||
async function startSubagentMcpServer(params) {
|
||||
async function startSubagentMcpServer(ctx) {
|
||||
const subagentToolState = {
|
||||
...params.ctx.toolState,
|
||||
selfSubagentId: params.subagentId,
|
||||
...ctx.toolState,
|
||||
backgroundProcesses: /* @__PURE__ */ new Map()
|
||||
};
|
||||
const subagentCtx = { ...params.ctx, toolState: subagentToolState };
|
||||
const subagentCtx = { ...ctx, toolState: subagentToolState };
|
||||
const tools = buildSubagentTools(subagentCtx);
|
||||
const startResult = await selectMcpPort(subagentCtx, tools);
|
||||
return { url: startResult.url, stop: () => startResult.server.stop() };
|
||||
@@ -144639,7 +144622,7 @@ import { join as join11 } from "node:path";
|
||||
// package.json
|
||||
var package_default = {
|
||||
name: "@pullfrog/pullfrog",
|
||||
version: "0.0.171",
|
||||
version: "0.0.169",
|
||||
type: "module",
|
||||
files: [
|
||||
"index.js",
|
||||
@@ -146040,7 +146023,7 @@ function configureGeminiSettings(ctx) {
|
||||
}
|
||||
|
||||
// agents/opencode.ts
|
||||
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync12 } from "node:fs";
|
||||
import { existsSync as existsSync7, mkdirSync as mkdirSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync12 } from "node:fs";
|
||||
import { join as join15 } from "node:path";
|
||||
import { performance as performance7 } from "node:perf_hooks";
|
||||
var OPENCODE_CLI_VERSION = "1.1.56";
|
||||
@@ -146061,6 +146044,101 @@ function detectProviderError(text) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function isRecord(value2) {
|
||||
return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
|
||||
}
|
||||
function getRecordProperty(ctx) {
|
||||
if (!isRecord(ctx.value)) {
|
||||
return void 0;
|
||||
}
|
||||
const propertyValue = ctx.value[ctx.key];
|
||||
if (!isRecord(propertyValue)) {
|
||||
return void 0;
|
||||
}
|
||||
return propertyValue;
|
||||
}
|
||||
function loadRepoOpenCodeConfig(ctx) {
|
||||
if (!existsSync7(ctx.repoConfigPath)) {
|
||||
log.info(`\xBB repo opencode.json not found at ${ctx.repoConfigPath}`);
|
||||
return void 0;
|
||||
}
|
||||
try {
|
||||
const rawConfig = readFileSync7(ctx.repoConfigPath, "utf-8");
|
||||
const parsedConfig = JSON.parse(rawConfig);
|
||||
if (!isRecord(parsedConfig)) {
|
||||
log.warning(`\xBB repo opencode.json is not an object: ${ctx.repoConfigPath}`);
|
||||
return void 0;
|
||||
}
|
||||
const providerConfig = getRecordProperty({ value: parsedConfig, key: "provider" });
|
||||
if (providerConfig) {
|
||||
const providerNames = Object.keys(providerConfig);
|
||||
log.info(`\xBB repo opencode provider config detected: ${providerNames.join(", ")}`);
|
||||
}
|
||||
const result = parsedConfig;
|
||||
log.info(`\xBB loaded repo opencode.json from ${ctx.repoConfigPath}`);
|
||||
return result;
|
||||
} catch (error49) {
|
||||
const errorMessage = error49 instanceof Error ? error49.message : String(error49);
|
||||
log.warning(`\xBB failed to parse repo opencode.json at ${ctx.repoConfigPath}: ${errorMessage}`);
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
function parseProviderFromModel(ctx) {
|
||||
const trimmedModel = ctx.model.trim();
|
||||
const slashIndex = trimmedModel.indexOf("/");
|
||||
if (slashIndex <= 0) {
|
||||
return void 0;
|
||||
}
|
||||
const providerId = trimmedModel.slice(0, slashIndex).trim().toLowerCase();
|
||||
if (!providerId) {
|
||||
return void 0;
|
||||
}
|
||||
return providerId;
|
||||
}
|
||||
function buildInlineConfigOverride(ctx) {
|
||||
const providerId = parseProviderFromModel({ model: ctx.model });
|
||||
if (!providerId) {
|
||||
return void 0;
|
||||
}
|
||||
const inlineConfig = {
|
||||
model: ctx.model,
|
||||
enabled_providers: [providerId]
|
||||
};
|
||||
return {
|
||||
providerId,
|
||||
content: JSON.stringify(inlineConfig)
|
||||
};
|
||||
}
|
||||
function readNonEmptyEnvVar(ctx) {
|
||||
const value2 = ctx.env[ctx.name];
|
||||
if (!value2) {
|
||||
return void 0;
|
||||
}
|
||||
const trimmed = value2.trim();
|
||||
if (!trimmed) {
|
||||
return void 0;
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
function resolveModelOverride(ctx) {
|
||||
if (ctx.effort === "mini") {
|
||||
const miniModel = readNonEmptyEnvVar({ env: ctx.env, name: "OPENCODE_MODEL_MINI" });
|
||||
if (miniModel) {
|
||||
return { model: miniModel, source: "OPENCODE_MODEL_MINI" };
|
||||
}
|
||||
}
|
||||
if (ctx.effort === "max") {
|
||||
const maxModel = readNonEmptyEnvVar({ env: ctx.env, name: "OPENCODE_MODEL_MAX" });
|
||||
if (maxModel) {
|
||||
return { model: maxModel, source: "OPENCODE_MODEL_MAX" };
|
||||
}
|
||||
}
|
||||
const baseModel = readNonEmptyEnvVar({ env: ctx.env, name: "OPENCODE_MODEL" });
|
||||
if (!baseModel) {
|
||||
return void 0;
|
||||
}
|
||||
return { model: baseModel, source: "OPENCODE_MODEL" };
|
||||
}
|
||||
async function installOpencode() {
|
||||
return await installFromNpmTarball({
|
||||
packageName: "opencode-ai",
|
||||
@@ -146079,10 +146157,13 @@ var opencode = agent({
|
||||
mkdirSync9(configDir, { recursive: true });
|
||||
configureOpenCode(ctx);
|
||||
const args2 = ["run", ctx.instructions.full, "--format", "json", "--print-logs"];
|
||||
const modelOverride = process.env.OPENCODE_MODEL;
|
||||
const modelOverride = resolveModelOverride({
|
||||
effort: ctx.payload.effort,
|
||||
env: process.env
|
||||
});
|
||||
if (modelOverride) {
|
||||
args2.push("--model", modelOverride);
|
||||
log.info(`\xBB model: ${modelOverride} (override)`);
|
||||
args2.push("--model", modelOverride.model);
|
||||
log.info(`\xBB model: ${modelOverride.model} (override via ${modelOverride.source})`);
|
||||
} else {
|
||||
log.info(`\xBB model: auto-selected by OpenCode`);
|
||||
}
|
||||
@@ -146094,6 +146175,28 @@ var opencode = agent({
|
||||
// set GOOGLE_GENERATIVE_AI_API_KEY alias for Google provider compatibility (if not already set)
|
||||
GOOGLE_GENERATIVE_AI_API_KEY: process.env.GOOGLE_GENERATIVE_AI_API_KEY || process.env.GEMINI_API_KEY
|
||||
};
|
||||
if (modelOverride) {
|
||||
const inlineOverride = buildInlineConfigOverride({ model: modelOverride.model });
|
||||
if (inlineOverride) {
|
||||
env2.OPENCODE_CONFIG_CONTENT = inlineOverride.content;
|
||||
log.info(
|
||||
`\xBB OpenCode inline config override enabled: provider=${inlineOverride.providerId}, model=${modelOverride.model}`
|
||||
);
|
||||
} else {
|
||||
log.warning(
|
||||
`\xBB skipping OpenCode inline config override: unable to parse provider from model "${modelOverride.model}"`
|
||||
);
|
||||
}
|
||||
}
|
||||
const hasOpenRouterKey = Boolean(env2.OPENROUTER_API_KEY);
|
||||
const hasAnthropicKey = Boolean(env2.ANTHROPIC_API_KEY);
|
||||
const hasOpenAiKey = Boolean(env2.OPENAI_API_KEY);
|
||||
const hasGoogleKey = Boolean(
|
||||
env2.GOOGLE_API_KEY || env2.GEMINI_API_KEY || env2.GOOGLE_GENERATIVE_AI_API_KEY
|
||||
);
|
||||
log.info(
|
||||
`\xBB provider key presence: OPENROUTER=${hasOpenRouterKey ? "set" : "unset"}, ANTHROPIC=${hasAnthropicKey ? "set" : "unset"}, OPENAI=${hasOpenAiKey ? "set" : "unset"}, GOOGLE=${hasGoogleKey ? "set" : "unset"}`
|
||||
);
|
||||
delete env2.GITHUB_TOKEN;
|
||||
const repoDir = process.cwd();
|
||||
log.debug(`\xBB starting OpenCode: ${cliPath} ${args2.join(" ")}`);
|
||||
@@ -146246,21 +146349,34 @@ function configureOpenCode(ctx) {
|
||||
const configDir = join15(ctx.tmpdir, ".config", "opencode");
|
||||
mkdirSync9(configDir, { recursive: true });
|
||||
const configPath = join15(configDir, "opencode.json");
|
||||
const opencodeMcpServers = {
|
||||
[ghPullfrogMcpName]: { type: "remote", url: ctx.mcpServerUrl }
|
||||
};
|
||||
const repoConfigPath = join15(process.cwd(), "opencode.json");
|
||||
const repoConfig = loadRepoOpenCodeConfig({ repoConfigPath });
|
||||
if (repoConfig?.model) {
|
||||
log.info(`\xBB repo opencode model configured: ${repoConfig.model}`);
|
||||
}
|
||||
const opencodeMcpServers = {};
|
||||
const repoMcpServers = getRecordProperty({ value: repoConfig, key: "mcp" });
|
||||
if (repoMcpServers) {
|
||||
Object.assign(opencodeMcpServers, repoMcpServers);
|
||||
}
|
||||
opencodeMcpServers[ghPullfrogMcpName] = { type: "remote", url: ctx.mcpServerUrl };
|
||||
const shell = ctx.payload.shell;
|
||||
const permission = {
|
||||
edit: "deny",
|
||||
read: "deny",
|
||||
bash: shell !== "enabled" ? "deny" : "allow",
|
||||
webfetch: ctx.payload.web === "disabled" ? "deny" : "allow",
|
||||
external_directory: "deny"
|
||||
};
|
||||
const config3 = {
|
||||
mcp: opencodeMcpServers,
|
||||
permission
|
||||
};
|
||||
const permission = {};
|
||||
const repoPermission = getRecordProperty({ value: repoConfig, key: "permission" });
|
||||
if (repoPermission) {
|
||||
Object.assign(permission, repoPermission);
|
||||
}
|
||||
permission.edit = "deny";
|
||||
permission.read = "deny";
|
||||
permission.bash = shell !== "enabled" ? "deny" : "allow";
|
||||
permission.webfetch = ctx.payload.web === "disabled" ? "deny" : "allow";
|
||||
permission.external_directory = "deny";
|
||||
const config3 = {};
|
||||
if (repoConfig) {
|
||||
Object.assign(config3, repoConfig);
|
||||
}
|
||||
config3.mcp = opencodeMcpServers;
|
||||
config3.permission = permission;
|
||||
const configJson = JSON.stringify(config3, null, 2);
|
||||
try {
|
||||
writeFileSync12(configPath, configJson, "utf-8");
|
||||
@@ -147348,7 +147464,12 @@ async function setupGit(params) {
|
||||
$("git", ["remote", "set-url", "origin", originUrl], { cwd: repoDir });
|
||||
params.toolState.pushUrl = originUrl;
|
||||
$("git", ["config", "--local", "credential.helper", ""], { cwd: repoDir });
|
||||
log.info("\xBB git authentication configured");
|
||||
if (params.event.is_pr !== true || !params.event.issue_number) {
|
||||
log.info("\xBB git authentication configured");
|
||||
return;
|
||||
}
|
||||
const prNumber = params.event.issue_number;
|
||||
await checkoutPrBranch(prNumber, params);
|
||||
}
|
||||
|
||||
// utils/time.ts
|
||||
|
||||
Reference in New Issue
Block a user