pullfrog 0.1.103 → 0.1.105
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/cli.mjs +380 -194
- package/dist/external.d.ts +3 -2
- package/dist/index.js +212 -61
- package/dist/internal/index.d.ts +1 -0
- package/dist/internal.js +52 -3
- package/dist/mcp/review.d.ts +1 -0
- package/dist/models.d.ts +21 -0
- package/dist/utils/agent.d.ts +9 -0
- package/dist/utils/apiKeys.d.ts +2 -0
- package/dist/utils/patchWorkflowRunFields.d.ts +4 -2
- package/dist/utils/rangeDiff.d.ts +2 -0
- package/dist/utils/runEffort.d.ts +10 -0
- package/package.json +1 -1
package/dist/external.d.ts
CHANGED
|
@@ -375,8 +375,9 @@ export interface WriteablePayload {
|
|
|
375
375
|
export type Payload = Readonly<WriteablePayload>;
|
|
376
376
|
/**
|
|
377
377
|
* Whether `find_similar_issues` is registered for this run: an entitled account,
|
|
378
|
-
* on an issue
|
|
379
|
-
* the
|
|
378
|
+
* on an issue or a run with no target (`pullfrog run "enrich issue #42"`), never a PR.
|
|
379
|
+
* Shared by the MCP registration and the prompt so the duplicate-detection
|
|
380
|
+
* instructions can never describe an absent tool.
|
|
380
381
|
*/
|
|
381
382
|
export declare function hasSimilarIssues(params: {
|
|
382
383
|
repoIntelligence: boolean;
|
package/dist/index.js
CHANGED
|
@@ -109939,9 +109939,34 @@ var AZURE_USE_CHAT_COMPLETIONS_ENV = "AZURE_USE_CHAT_COMPLETIONS";
|
|
|
109939
109939
|
var OPENAI_COMPATIBLE_PROVIDER = "openai-compatible";
|
|
109940
109940
|
var OPENAI_COMPATIBLE_BASE_URL_ENV = "OPENAI_COMPATIBLE_BASE_URL";
|
|
109941
109941
|
var OPENAI_COMPATIBLE_API_KEY_ENV = "OPENAI_COMPATIBLE_API_KEY";
|
|
109942
|
-
var OPENAI_COMPATIBLE_MODEL_ENV = "OPENAI_COMPATIBLE_MODEL";
|
|
109943
109942
|
var OPENAI_COMPATIBLE_CONTEXT_ENV = "OPENAI_COMPATIBLE_CONTEXT";
|
|
109944
109943
|
var OPENAI_COMPATIBLE_MAX_OUTPUT_ENV = "OPENAI_COMPATIBLE_MAX_OUTPUT";
|
|
109944
|
+
function isOpenAICompatibleSetName(name) {
|
|
109945
|
+
return name !== "byok" && name.length <= 32 && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name);
|
|
109946
|
+
}
|
|
109947
|
+
function openAICompatibleSetName(slug2) {
|
|
109948
|
+
const prefix = `${OPENAI_COMPATIBLE_PROVIDER}/`;
|
|
109949
|
+
if (!slug2.startsWith(prefix)) return void 0;
|
|
109950
|
+
const name = slug2.slice(prefix.length);
|
|
109951
|
+
return isOpenAICompatibleSetName(name) ? name : void 0;
|
|
109952
|
+
}
|
|
109953
|
+
var OPENAI_COMPATIBLE_FIELDS = [
|
|
109954
|
+
"baseUrl",
|
|
109955
|
+
"apiKey",
|
|
109956
|
+
"model",
|
|
109957
|
+
"context",
|
|
109958
|
+
"maxOutput"
|
|
109959
|
+
];
|
|
109960
|
+
function openAICompatibleEnv(name) {
|
|
109961
|
+
const prefix = `OPENAI_COMPATIBLE_${name ? `${name.toUpperCase().replaceAll("-", "_")}_` : ""}`;
|
|
109962
|
+
return {
|
|
109963
|
+
baseUrl: `${prefix}BASE_URL`,
|
|
109964
|
+
apiKey: `${prefix}API_KEY`,
|
|
109965
|
+
model: `${prefix}MODEL`,
|
|
109966
|
+
context: `${prefix}CONTEXT`,
|
|
109967
|
+
maxOutput: `${prefix}MAX_OUTPUT`
|
|
109968
|
+
};
|
|
109969
|
+
}
|
|
109945
109970
|
var PROVIDER_GATEWAY_URL_ENV = {
|
|
109946
109971
|
anthropic: "ANTHROPIC_BASE_URL",
|
|
109947
109972
|
openai: "OPENAI_BASE_URL"
|
|
@@ -109986,7 +110011,7 @@ function findDanglingPromptToolRefs(params) {
|
|
|
109986
110011
|
return [...new Set(extractMcpToolRefs(params.agentId, params.prompt))].filter((name) => !registered.has(name)).sort();
|
|
109987
110012
|
}
|
|
109988
110013
|
function hasSimilarIssues(params) {
|
|
109989
|
-
return params.repoIntelligence &&
|
|
110014
|
+
return params.repoIntelligence && !params.event.is_pr;
|
|
109990
110015
|
}
|
|
109991
110016
|
|
|
109992
110017
|
// utils/activity.ts
|
|
@@ -112115,7 +112140,11 @@ function resolveRunEffort(ctx) {
|
|
|
112115
112140
|
const configured = ctx.payload.effort !== void 0;
|
|
112116
112141
|
const position = ctx.payload.effort ?? DEFAULT_EFFORT_POSITION;
|
|
112117
112142
|
const alias = resolveRunAlias(ctx);
|
|
112118
|
-
if (!alias)
|
|
112143
|
+
if (!alias) {
|
|
112144
|
+
const declarable = configured && !ctx.resolvedModel?.startsWith(`${AZURE_PROVIDER}/`);
|
|
112145
|
+
const rung = declarable && ctx.declaredVariants ? resolveRung({ position, published: ctx.declaredVariants }) : void 0;
|
|
112146
|
+
return { position, configured, rung, alias: void 0, declarable };
|
|
112147
|
+
}
|
|
112119
112148
|
return {
|
|
112120
112149
|
position,
|
|
112121
112150
|
configured,
|
|
@@ -112124,7 +112153,8 @@ function resolveRunEffort(ctx) {
|
|
|
112124
112153
|
position,
|
|
112125
112154
|
useOpenRouter: !!ctx.payload.proxyModel
|
|
112126
112155
|
}),
|
|
112127
|
-
alias
|
|
112156
|
+
alias,
|
|
112157
|
+
declarable: false
|
|
112128
112158
|
};
|
|
112129
112159
|
}
|
|
112130
112160
|
|
|
@@ -112141,7 +112171,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
112141
112171
|
// package.json
|
|
112142
112172
|
var package_default = {
|
|
112143
112173
|
name: "pullfrog",
|
|
112144
|
-
version: "0.1.
|
|
112174
|
+
version: "0.1.105",
|
|
112145
112175
|
cliContractVersion: "1",
|
|
112146
112176
|
type: "module",
|
|
112147
112177
|
bin: {
|
|
@@ -124270,6 +124300,17 @@ function parseModel2(value2) {
|
|
|
124270
124300
|
if (slash <= 0) return void 0;
|
|
124271
124301
|
return { providerID: value2.slice(0, slash), modelID: value2.slice(slash + 1) };
|
|
124272
124302
|
}
|
|
124303
|
+
async function resolveDeclaredRung(ctx, params) {
|
|
124304
|
+
const resp = await params.client.config.providers();
|
|
124305
|
+
const provider2 = resp.data?.providers.find((p) => p.id === params.model.providerID);
|
|
124306
|
+
const variants = provider2?.models[params.model.modelID]?.variants ?? {};
|
|
124307
|
+
const name = `${params.model.providerID}/${params.model.modelID}`;
|
|
124308
|
+
const rung = resolveRunEffort({ ...ctx, declaredVariants: Object.keys(variants) }).rung;
|
|
124309
|
+
log.info(
|
|
124310
|
+
`\xBB effort: ${rung ? `${rung} (declared by opencode for ${name})` : `not applied \u2014 opencode declares no variants for ${name}`}`
|
|
124311
|
+
);
|
|
124312
|
+
return rung;
|
|
124313
|
+
}
|
|
124273
124314
|
function bootOpencodeServer(params) {
|
|
124274
124315
|
const logLevel = isDebugEnabled() ? "INFO" : "ERROR";
|
|
124275
124316
|
if (logLevel !== "ERROR") log.info(`\xBB opencode log level: ${logLevel} (debug run)`);
|
|
@@ -124924,6 +124965,11 @@ var opencode = agent({
|
|
|
124924
124965
|
log.info(`\xBB opencode session: ${sessionID}`);
|
|
124925
124966
|
const labeler = new SessionLabeler();
|
|
124926
124967
|
labeler.labelFor(sessionID);
|
|
124968
|
+
const sdkModel = parseModel2(model);
|
|
124969
|
+
const variant = effort.declarable && sdkModel ? await resolveDeclaredRung(
|
|
124970
|
+
{ ...ctx, resolvedModel: rawModel },
|
|
124971
|
+
{ client: client2, model: sdkModel }
|
|
124972
|
+
) : effort.rung;
|
|
124927
124973
|
const runnerCtx = {
|
|
124928
124974
|
client: client2,
|
|
124929
124975
|
sessionID,
|
|
@@ -124932,7 +124978,7 @@ var opencode = agent({
|
|
|
124932
124978
|
labeler,
|
|
124933
124979
|
toolState: ctx.toolState,
|
|
124934
124980
|
// `rawModel` folds in the auto-select pick — see the opencode.ts twin.
|
|
124935
|
-
variant
|
|
124981
|
+
variant,
|
|
124936
124982
|
todoTracker: ctx.todoTracker,
|
|
124937
124983
|
onActivityTimeout: ctx.onActivityTimeout,
|
|
124938
124984
|
onTurnRecovered: ctx.onTurnRecovered,
|
|
@@ -124994,7 +125040,6 @@ var opencode = agent({
|
|
|
124994
125040
|
timeoutMs: AGENT_ACTIVITY_TIMEOUT_MS,
|
|
124995
125041
|
abortTurn: () => turnController.abort()
|
|
124996
125042
|
});
|
|
124997
|
-
const sdkModel = parseModel2(model);
|
|
124998
125043
|
let salvagesLeft = 1;
|
|
124999
125044
|
const runTurn = async (text) => {
|
|
125000
125045
|
const attempt = (prompt) => {
|
|
@@ -190129,14 +190174,18 @@ async function runGitLines(args2) {
|
|
|
190129
190174
|
// utils/rangeDiff.ts
|
|
190130
190175
|
function computeIncrementalDiff(params) {
|
|
190131
190176
|
try {
|
|
190177
|
+
const tips = [`origin/${params.baseBranch}`, ...params.formerBaseTips];
|
|
190178
|
+
const oldBase = versionBase({ sha: params.beforeSha, tips });
|
|
190179
|
+
const newBase = versionBase({ sha: params.headSha, tips });
|
|
190132
190180
|
const raw2 = $(
|
|
190133
190181
|
"sh",
|
|
190134
190182
|
[
|
|
190135
190183
|
"-c",
|
|
190136
|
-
'
|
|
190184
|
+
'git range-diff --no-color --creation-factor=1000 "$1..$(git commit-tree "$2^{tree}" -p "$1" -m x)" "$3..$(git commit-tree "$4^{tree}" -p "$3" -m x)"',
|
|
190137
190185
|
"--",
|
|
190186
|
+
oldBase,
|
|
190138
190187
|
params.beforeSha,
|
|
190139
|
-
|
|
190188
|
+
newBase,
|
|
190140
190189
|
params.headSha
|
|
190141
190190
|
],
|
|
190142
190191
|
{ log: false }
|
|
@@ -190149,6 +190198,27 @@ function computeIncrementalDiff(params) {
|
|
|
190149
190198
|
return { status: "unavailable", reason };
|
|
190150
190199
|
}
|
|
190151
190200
|
}
|
|
190201
|
+
function git(args2) {
|
|
190202
|
+
return $("git", args2, { log: false }).trim();
|
|
190203
|
+
}
|
|
190204
|
+
function versionBase(params) {
|
|
190205
|
+
let best;
|
|
190206
|
+
for (const tip of params.tips) {
|
|
190207
|
+
let sha;
|
|
190208
|
+
try {
|
|
190209
|
+
sha = git(["merge-base", params.sha, tip]);
|
|
190210
|
+
} catch {
|
|
190211
|
+
continue;
|
|
190212
|
+
}
|
|
190213
|
+
const commits = Number(git(["rev-list", "--count", `${sha}..${params.sha}`]));
|
|
190214
|
+
if (!best || commits < best.commits) best = { tip, sha, commits };
|
|
190215
|
+
}
|
|
190216
|
+
if (!best) throw new Error(`no merge base between ${params.sha} and the base branch`);
|
|
190217
|
+
if (best.tip !== params.tips[0]) {
|
|
190218
|
+
log.info(`\xBB incremental diff: ${params.sha.slice(0, 7)} sits on former base tip ${best.tip}`);
|
|
190219
|
+
}
|
|
190220
|
+
return best.sha;
|
|
190221
|
+
}
|
|
190152
190222
|
function isDiffPrefix(ch) {
|
|
190153
190223
|
return ch === " " || ch === "+" || ch === "-";
|
|
190154
190224
|
}
|
|
@@ -190176,14 +190246,28 @@ function postProcessRangeDiff(raw2, contextLines = 3) {
|
|
|
190176
190246
|
if (lastHunkHdr?.seq === line.seq) hunkHdrEmitted = true;
|
|
190177
190247
|
}
|
|
190178
190248
|
function flushBefore() {
|
|
190179
|
-
|
|
190180
|
-
|
|
190181
|
-
|
|
190182
|
-
|
|
190183
|
-
|
|
190249
|
+
const headers = [
|
|
190250
|
+
outerHdrEmitted ? null : lastOuterHdr,
|
|
190251
|
+
fileHdrEmitted ? null : lastFileHdr,
|
|
190252
|
+
hunkHdrEmitted ? null : lastHunkHdr
|
|
190253
|
+
].filter((line) => line !== null);
|
|
190254
|
+
const pending = [...headers, ...beforeBuf.filter((line) => line.seq > lastEmittedSeq)].sort(
|
|
190255
|
+
(a, b) => a.seq - b.seq
|
|
190256
|
+
);
|
|
190257
|
+
for (const [i, line] of pending.entries()) {
|
|
190258
|
+
if (pending[i - 1]?.seq !== line.seq) emit(line);
|
|
190184
190259
|
}
|
|
190185
190260
|
beforeBuf.length = 0;
|
|
190186
190261
|
}
|
|
190262
|
+
function isFileHeader(pos) {
|
|
190263
|
+
return raw2.startsWith("## ", pos) && !raw2.startsWith("## Commit message", pos);
|
|
190264
|
+
}
|
|
190265
|
+
function markFileHeader(line, emitted) {
|
|
190266
|
+
lastFileHdr = line;
|
|
190267
|
+
fileHdrEmitted = emitted;
|
|
190268
|
+
lastHunkHdr = null;
|
|
190269
|
+
hunkHdrEmitted = true;
|
|
190270
|
+
}
|
|
190187
190271
|
let cursor = 0;
|
|
190188
190272
|
while (cursor < raw2.length) {
|
|
190189
190273
|
const eol = raw2.indexOf("\n", cursor);
|
|
@@ -190193,7 +190277,8 @@ function postProcessRangeDiff(raw2, contextLines = 3) {
|
|
|
190193
190277
|
continue;
|
|
190194
190278
|
}
|
|
190195
190279
|
if (raw2.startsWith(" @@", cursor)) {
|
|
190196
|
-
|
|
190280
|
+
const label = raw2.slice(cursor + 4, lineEnd);
|
|
190281
|
+
lastOuterHdr = label === "@@ Metadata" || label === "@@ Commit message" ? null : { prefix: "", from: cursor + 4, to: lineEnd, seq };
|
|
190197
190282
|
outerHdrEmitted = false;
|
|
190198
190283
|
lastFileHdr = null;
|
|
190199
190284
|
fileHdrEmitted = true;
|
|
@@ -190214,13 +190299,13 @@ function postProcessRangeDiff(raw2, contextLines = 3) {
|
|
|
190214
190299
|
} else if (isDiffPrefix(raw2[contentPos])) {
|
|
190215
190300
|
isChange = isOuterChange;
|
|
190216
190301
|
line = { prefix, from: contentPos + 1, to: lineEnd, seq };
|
|
190302
|
+
if (raw2[contentPos] === " " && isFileHeader(contentPos + 1)) {
|
|
190303
|
+
markFileHeader(line, isChange);
|
|
190304
|
+
}
|
|
190217
190305
|
} else {
|
|
190218
190306
|
line = { prefix, from: contentPos, to: lineEnd, seq };
|
|
190219
|
-
if (
|
|
190220
|
-
|
|
190221
|
-
fileHdrEmitted = false;
|
|
190222
|
-
lastHunkHdr = null;
|
|
190223
|
-
hunkHdrEmitted = true;
|
|
190307
|
+
if (isFileHeader(contentPos)) {
|
|
190308
|
+
markFileHeader(line, false);
|
|
190224
190309
|
} else if (raw2.startsWith("@@", contentPos) && !raw2.startsWith("@@ Metadata", contentPos)) {
|
|
190225
190310
|
lastHunkHdr = line;
|
|
190226
190311
|
hunkHdrEmitted = false;
|
|
@@ -196213,6 +196298,7 @@ var STRING_KEYS = [
|
|
|
196213
196298
|
"prNodeId",
|
|
196214
196299
|
"issueNodeId",
|
|
196215
196300
|
"reviewNodeId",
|
|
196301
|
+
"reviewedCommitSha",
|
|
196216
196302
|
"planCommentNodeId",
|
|
196217
196303
|
"summarySnapshot",
|
|
196218
196304
|
"model",
|
|
@@ -196343,13 +196429,16 @@ function buildImplementPlanLink(ctx, issueNumber, commentId) {
|
|
|
196343
196429
|
const apiUrl = getApiUrl();
|
|
196344
196430
|
return `[Implement plan \u2794](${apiUrl}/trigger/${ctx.repo.owner}/${ctx.repo.name}/${issueNumber}?action=implement&comment_id=${commentId})`;
|
|
196345
196431
|
}
|
|
196346
|
-
function
|
|
196432
|
+
function runIssueNumber(ctx) {
|
|
196433
|
+
return ctx.payload.event.issue_number ?? primaryRepoState(ctx.toolState).issueNumber;
|
|
196434
|
+
}
|
|
196435
|
+
function buildEnrichmentLinks(ctx, issueNumber) {
|
|
196347
196436
|
const event = ctx.payload.event;
|
|
196348
|
-
|
|
196349
|
-
|
|
196350
|
-
|
|
196437
|
+
const mode = ctx.toolState.selectedMode;
|
|
196438
|
+
const enriching = mode === "Enrich" || event.trigger === "issues_opened" && !ctx.payload.prompt && mode !== "Build";
|
|
196439
|
+
if (issueNumber === void 0 || event.is_pr || !enriching) return [];
|
|
196351
196440
|
return [
|
|
196352
|
-
`[Implement a fix \u2794](${getApiUrl()}/trigger/${ctx.repo.owner}/${ctx.repo.name}/${
|
|
196441
|
+
`[Implement a fix \u2794](${getApiUrl()}/trigger/${ctx.repo.owner}/${ctx.repo.name}/${issueNumber}?action=build)`
|
|
196353
196442
|
];
|
|
196354
196443
|
}
|
|
196355
196444
|
function addFooter(ctx, body) {
|
|
@@ -196385,10 +196474,11 @@ function CreateCommentTool(ctx) {
|
|
|
196385
196474
|
});
|
|
196386
196475
|
ctx.toolState.wasUpdated = true;
|
|
196387
196476
|
log.info(`\xBB created comment ${result.data.id}`);
|
|
196388
|
-
|
|
196477
|
+
const ownTarget = issueNumber === runIssueNumber(ctx);
|
|
196478
|
+
if (ownTarget) {
|
|
196389
196479
|
ctx.toolState.standaloneCommentId = result.data.id;
|
|
196390
196480
|
}
|
|
196391
|
-
const enrichmentLinks =
|
|
196481
|
+
const enrichmentLinks = ownTarget ? buildEnrichmentLinks(ctx, issueNumber) : [];
|
|
196392
196482
|
if (commentType === "Plan" || enrichmentLinks.length > 0) {
|
|
196393
196483
|
if (commentType === "Plan" && result.data.node_id) {
|
|
196394
196484
|
await patchWorkflowRunFields(ctx, { planCommentNodeId: result.data.node_id });
|
|
@@ -196468,9 +196558,9 @@ async function reportProgress(ctx, params) {
|
|
|
196468
196558
|
if (params.liveProgress && !ctx.payload.progressComments) {
|
|
196469
196559
|
return { body, action: "skipped" };
|
|
196470
196560
|
}
|
|
196471
|
-
const issueNumber =
|
|
196561
|
+
const issueNumber = runIssueNumber(ctx);
|
|
196472
196562
|
const isPlanMode = ctx.toolState.selectedMode === "Plan";
|
|
196473
|
-
const enrichmentLinks = params.liveProgress ? [] : buildEnrichmentLinks(ctx);
|
|
196563
|
+
const enrichmentLinks = params.liveProgress ? [] : buildEnrichmentLinks(ctx, issueNumber);
|
|
196474
196564
|
const apiCtx = { octokit: ctx.octokit, owner: ctx.repo.owner, repo: ctx.repo.name };
|
|
196475
196565
|
if (target_plan_comment === true && ctx.toolState.existingPlanCommentId === void 0) {
|
|
196476
196566
|
log.warning("target_plan_comment requested but no existingPlanCommentId in tool state");
|
|
@@ -197377,7 +197467,10 @@ async function createAndSubmitWithFooter(ctx, params, opts) {
|
|
|
197377
197467
|
}
|
|
197378
197468
|
}
|
|
197379
197469
|
async function reportReviewNodeId(ctx, params) {
|
|
197380
|
-
await patchWorkflowRunFields(ctx, {
|
|
197470
|
+
await patchWorkflowRunFields(ctx, {
|
|
197471
|
+
reviewNodeId: params.nodeId,
|
|
197472
|
+
reviewedCommitSha: params.reviewedSha
|
|
197473
|
+
});
|
|
197381
197474
|
}
|
|
197382
197475
|
|
|
197383
197476
|
// utils/setup.ts
|
|
@@ -197971,6 +198064,34 @@ function describeHead(h) {
|
|
|
197971
198064
|
if (h.kind === "branch") return `branch \`${h.name}\``;
|
|
197972
198065
|
return `detached HEAD \`${h.sha}\``;
|
|
197973
198066
|
}
|
|
198067
|
+
var FORMER_BASE_TIPS_QUERY = `
|
|
198068
|
+
query ($owner: String!, $repo: String!, $number: Int!) {
|
|
198069
|
+
repository(owner: $owner, name: $repo) {
|
|
198070
|
+
pullRequest(number: $number) {
|
|
198071
|
+
timelineItems(itemTypes: [BASE_REF_FORCE_PUSHED_EVENT], last: 100) {
|
|
198072
|
+
nodes { ... on BaseRefForcePushedEvent { beforeCommit { oid } } }
|
|
198073
|
+
}
|
|
198074
|
+
}
|
|
198075
|
+
}
|
|
198076
|
+
}
|
|
198077
|
+
`;
|
|
198078
|
+
async function getFormerBaseTips(ctx, pullNumber) {
|
|
198079
|
+
try {
|
|
198080
|
+
const response = await ctx.octokit.graphql(FORMER_BASE_TIPS_QUERY, {
|
|
198081
|
+
owner: ctx.repo.owner,
|
|
198082
|
+
repo: ctx.repo.name,
|
|
198083
|
+
number: pullNumber
|
|
198084
|
+
});
|
|
198085
|
+
return response.repository.pullRequest.timelineItems.nodes.flatMap(
|
|
198086
|
+
(node3) => node3.beforeCommit ? [node3.beforeCommit.oid] : []
|
|
198087
|
+
);
|
|
198088
|
+
} catch (error63) {
|
|
198089
|
+
log.warning(
|
|
198090
|
+
`\xBB former base tips unavailable: ${error63 instanceof Error ? error63.message : String(error63)}`
|
|
198091
|
+
);
|
|
198092
|
+
return [];
|
|
198093
|
+
}
|
|
198094
|
+
}
|
|
197974
198095
|
function CheckoutPrTool(ctx) {
|
|
197975
198096
|
const runCheckout = async (pull_number, signal) => {
|
|
197976
198097
|
const prResponse = await ctx.octokit.rest.pulls.get({
|
|
@@ -198023,7 +198144,8 @@ function CheckoutPrTool(ctx) {
|
|
|
198023
198144
|
const incremental = computeIncrementalDiff({
|
|
198024
198145
|
baseBranch: pr.baseRef,
|
|
198025
198146
|
beforeSha: primary.beforeSha,
|
|
198026
|
-
headSha: checkoutSha
|
|
198147
|
+
headSha: checkoutSha,
|
|
198148
|
+
formerBaseTips: await getFormerBaseTips(ctx, pull_number)
|
|
198027
198149
|
});
|
|
198028
198150
|
if (incremental.status === "ok") {
|
|
198029
198151
|
incrementalDiffPath = join16(
|
|
@@ -199449,7 +199571,7 @@ function GetIssueCommentsTool(ctx) {
|
|
|
199449
199571
|
description: "Get all comments for a GitHub issue. Returns all comments including the issue body and all subsequent discussion comments. Example: `get_issue_comments({ issue_number: 1234 })`.",
|
|
199450
199572
|
parameters: GetIssueComments,
|
|
199451
199573
|
execute: execute(async ({ issue_number }) => {
|
|
199452
|
-
primaryRepoState(ctx.toolState).issueNumber
|
|
199574
|
+
primaryRepoState(ctx.toolState).issueNumber ??= issue_number;
|
|
199453
199575
|
const comments = await ctx.octokit.paginate(ctx.octokit.rest.issues.listComments, {
|
|
199454
199576
|
owner: ctx.repo.owner,
|
|
199455
199577
|
repo: ctx.repo.name,
|
|
@@ -199487,7 +199609,7 @@ function GetIssueEventsTool(ctx) {
|
|
|
199487
199609
|
description: "Get timeline events for a GitHub issue that aren't reflected in the current state. Returns cross-references to other issues/PRs and commit references. Note: current labels, assignees, state, and milestone are already available via get_issue.",
|
|
199488
199610
|
parameters: GetIssueEvents,
|
|
199489
199611
|
execute: execute(async ({ issue_number }) => {
|
|
199490
|
-
primaryRepoState(ctx.toolState).issueNumber
|
|
199612
|
+
primaryRepoState(ctx.toolState).issueNumber ??= issue_number;
|
|
199491
199613
|
const events = await ctx.octokit.paginate(ctx.octokit.rest.issues.listEventsForTimeline, {
|
|
199492
199614
|
owner: ctx.repo.owner,
|
|
199493
199615
|
repo: ctx.repo.name,
|
|
@@ -199571,7 +199693,7 @@ function IssueInfoTool(ctx) {
|
|
|
199571
199693
|
tmpdir: ctx.tmpdir,
|
|
199572
199694
|
githubToken: ctx.githubInstallationToken
|
|
199573
199695
|
});
|
|
199574
|
-
primaryRepoState(ctx.toolState).issueNumber
|
|
199696
|
+
primaryRepoState(ctx.toolState).issueNumber ??= issue_number;
|
|
199575
199697
|
const hints = [];
|
|
199576
199698
|
if (data.comments > 0) {
|
|
199577
199699
|
hints.push("use get_issue_comments to retrieve all comments for this issue");
|
|
@@ -201039,8 +201161,11 @@ function SimilarIssuesTool(ctx) {
|
|
|
201039
201161
|
mutates: true,
|
|
201040
201162
|
parameters: SimilarIssues,
|
|
201041
201163
|
execute: execute(async (input2) => {
|
|
201042
|
-
|
|
201043
|
-
|
|
201164
|
+
const repoState = primaryRepoState(ctx.toolState);
|
|
201165
|
+
repoState.issueNumber ??= input2.issue_number;
|
|
201166
|
+
const subject = ctx.payload.event.issue_number ?? repoState.issueNumber;
|
|
201167
|
+
if (ctx.payload.event.is_pr || subject !== input2.issue_number) {
|
|
201168
|
+
throw new Error(`find_similar_issues is limited to this run's issue, #${subject}`);
|
|
201044
201169
|
}
|
|
201045
201170
|
const response = await apiFetch({
|
|
201046
201171
|
path: `/api/repo/${ctx.repo.owner}/${ctx.repo.name}/issues/${input2.issue_number}/similar`,
|
|
@@ -201322,7 +201447,7 @@ function buildCommonTools(ctx, outputSchema) {
|
|
|
201322
201447
|
if (hasSimilarIssues({ repoIntelligence: ctx.repoIntelligence, event: ctx.payload.event })) {
|
|
201323
201448
|
tools.push(SimilarIssuesTool(ctx));
|
|
201324
201449
|
}
|
|
201325
|
-
if (
|
|
201450
|
+
if (!ctx.payload.event.is_pr) {
|
|
201326
201451
|
tools.push(SearchIssuesTool(ctx));
|
|
201327
201452
|
}
|
|
201328
201453
|
const isStandalone = ctx.payload.event.trigger === "unknown";
|
|
@@ -201720,11 +201845,11 @@ function buildOpenAICompatibleSetupError(params) {
|
|
|
201720
201845
|
missing: params.missing,
|
|
201721
201846
|
where: `Set them on the OpenAI-compatible provider in [Pullfrog's provider settings](${getApiUrl()}/console/${params.owner}#billing), or in the workflow's \`env:\` block with the key as an [Actions secret](${secretsUrl(params)}):`,
|
|
201722
201847
|
env: [
|
|
201723
|
-
`${
|
|
201724
|
-
`${
|
|
201725
|
-
`${
|
|
201726
|
-
`${
|
|
201727
|
-
`${
|
|
201848
|
+
`${params.env.baseUrl}: https://your-endpoint.example.com/v1`,
|
|
201849
|
+
`${params.env.apiKey}: \${{ secrets.${params.env.apiKey} }}`,
|
|
201850
|
+
`${params.env.model}: <model-id>`,
|
|
201851
|
+
`${params.env.context}: "128000"`,
|
|
201852
|
+
`${params.env.maxOutput}: "16384"`
|
|
201728
201853
|
],
|
|
201729
201854
|
note: "Use the real context and output limits of the model your endpoint serves: Pullfrog can't discover them, and without them long runs grow until the endpoint refuses them.",
|
|
201730
201855
|
guide: "https://docs.pullfrog.com/openai-compatible"
|
|
@@ -201758,17 +201883,16 @@ function hasPositiveNumberEnvVar(name) {
|
|
|
201758
201883
|
return Number(process.env[name]) > 0;
|
|
201759
201884
|
}
|
|
201760
201885
|
function validateOpenAICompatibleSetup(params) {
|
|
201886
|
+
const env2 = openAICompatibleEnv(params.set);
|
|
201761
201887
|
const missing = [];
|
|
201762
|
-
if (!hasEnvVar2(
|
|
201763
|
-
if (!hasEnvVar2(
|
|
201764
|
-
if (!hasEnvVar2(
|
|
201765
|
-
if (!hasPositiveNumberEnvVar(
|
|
201766
|
-
|
|
201767
|
-
if (!hasPositiveNumberEnvVar(OPENAI_COMPATIBLE_MAX_OUTPUT_ENV))
|
|
201768
|
-
missing.push(OPENAI_COMPATIBLE_MAX_OUTPUT_ENV);
|
|
201888
|
+
if (!hasEnvVar2(env2.baseUrl)) missing.push(env2.baseUrl);
|
|
201889
|
+
if (!hasEnvVar2(env2.apiKey)) missing.push(env2.apiKey);
|
|
201890
|
+
if (!hasEnvVar2(env2.model)) missing.push(env2.model);
|
|
201891
|
+
if (!hasPositiveNumberEnvVar(env2.context)) missing.push(env2.context);
|
|
201892
|
+
if (!hasPositiveNumberEnvVar(env2.maxOutput)) missing.push(env2.maxOutput);
|
|
201769
201893
|
if (missing.length > 0) {
|
|
201770
201894
|
throw new Error(
|
|
201771
|
-
buildOpenAICompatibleSetupError({ owner: params.owner, name: params.name, missing })
|
|
201895
|
+
buildOpenAICompatibleSetupError({ owner: params.owner, name: params.name, missing, env: env2 })
|
|
201772
201896
|
);
|
|
201773
201897
|
}
|
|
201774
201898
|
}
|
|
@@ -201824,7 +201948,11 @@ function validateAgentApiKey(params) {
|
|
|
201824
201948
|
return;
|
|
201825
201949
|
}
|
|
201826
201950
|
if (params.model.startsWith(`${OPENAI_COMPATIBLE_PROVIDER}/`)) {
|
|
201827
|
-
validateOpenAICompatibleSetup({
|
|
201951
|
+
validateOpenAICompatibleSetup({
|
|
201952
|
+
owner: params.owner,
|
|
201953
|
+
name: params.name,
|
|
201954
|
+
set: params.openAICompatibleSet
|
|
201955
|
+
});
|
|
201828
201956
|
return;
|
|
201829
201957
|
}
|
|
201830
201958
|
if (params.model.startsWith(`${AZURE_PROVIDER}/`)) {
|
|
@@ -202138,17 +202266,37 @@ function resolveSlug(slug2) {
|
|
|
202138
202266
|
}
|
|
202139
202267
|
return `${AZURE_PROVIDER}/${deployment}`;
|
|
202140
202268
|
}
|
|
202141
|
-
|
|
202142
|
-
|
|
202269
|
+
const set2 = heldOpenAICompatibleSet(slug2);
|
|
202270
|
+
if (alias?.routing === "openai-compatible" || set2) {
|
|
202271
|
+
const modelEnv = openAICompatibleEnv(set2).model;
|
|
202272
|
+
const modelId = process.env[modelEnv]?.trim();
|
|
202143
202273
|
if (!modelId) {
|
|
202144
202274
|
throw new Error(
|
|
202145
|
-
`\`${
|
|
202275
|
+
`\`${modelEnv}\` ${BYOK_SLUG_MARKER} \`${slug2}\`. Set it to the model ID your endpoint serves, on the OpenAI-compatible provider in Pullfrog or in the workflow's \`env:\` block. [OpenAI-compatible setup guide](https://docs.pullfrog.com/openai-compatible)`
|
|
202146
202276
|
);
|
|
202147
202277
|
}
|
|
202148
202278
|
return `${OPENAI_COMPATIBLE_PROVIDER}/${modelId}`;
|
|
202149
202279
|
}
|
|
202150
202280
|
return resolveCliModel(slug2);
|
|
202151
202281
|
}
|
|
202282
|
+
function heldOpenAICompatibleSet(slug2) {
|
|
202283
|
+
const set2 = openAICompatibleSetName(slug2);
|
|
202284
|
+
return set2 && Object.values(openAICompatibleEnv(set2)).some(hasEnvVar3) ? set2 : void 0;
|
|
202285
|
+
}
|
|
202286
|
+
function activateOpenAICompatibleSet(ctx) {
|
|
202287
|
+
const slug2 = process.env.PULLFROG_MODEL?.trim() || ctx.slug?.trim();
|
|
202288
|
+
const set2 = slug2 ? heldOpenAICompatibleSet(slug2) : void 0;
|
|
202289
|
+
if (!set2) return void 0;
|
|
202290
|
+
const from = openAICompatibleEnv(set2);
|
|
202291
|
+
const to = openAICompatibleEnv();
|
|
202292
|
+
for (const field of OPENAI_COMPATIBLE_FIELDS) {
|
|
202293
|
+
const value2 = process.env[from[field]];
|
|
202294
|
+
if (value2 === void 0) delete process.env[to[field]];
|
|
202295
|
+
else process.env[to[field]] = value2;
|
|
202296
|
+
}
|
|
202297
|
+
log.info(`\xBB using the OpenAI-compatible endpoint "${set2}"`);
|
|
202298
|
+
return set2;
|
|
202299
|
+
}
|
|
202152
202300
|
function resolveModel(ctx) {
|
|
202153
202301
|
const envModel = process.env.PULLFROG_MODEL?.trim();
|
|
202154
202302
|
if (envModel) {
|
|
@@ -203191,7 +203339,7 @@ Rules:
|
|
|
203191
203339
|
### GitHub
|
|
203192
203340
|
|
|
203193
203341
|
Use MCP tools from ${pullfrogMcpName} for all GitHub operations. Never use the \`gh\` CLI \u2014 it is not authenticated and will fail. The MCP tools handle authentication and enforce permissions.
|
|
203194
|
-
${hasSimilarIssues({ repoIntelligence: ctx.repoIntelligence, event: ctx.payload.event }) ? `
|
|
203342
|
+
${ctx.payload.event.issue_number !== void 0 && hasSimilarIssues({ repoIntelligence: ctx.repoIntelligence, event: ctx.payload.event }) ? `
|
|
203195
203343
|
#### Duplicate detection (enabled for this repository)
|
|
203196
203344
|
|
|
203197
203345
|
Call \`${t("find_similar_issues")}\` for #${ctx.payload.event.issue_number} before planning. If it duplicates an existing issue, link that instead of producing a plan; never close or label on similarity alone.
|
|
@@ -204661,7 +204809,7 @@ async function postReviewCleanup(ctx) {
|
|
|
204661
204809
|
if (!review) {
|
|
204662
204810
|
const checkoutSha = primaryRepoState(ctx.toolState).checkoutSha;
|
|
204663
204811
|
if (ctx.toolState.selectedMode === "IncrementalReview" && checkoutSha) {
|
|
204664
|
-
await patchWorkflowRunFields(ctx, { reviewSkipped: true });
|
|
204812
|
+
await patchWorkflowRunFields(ctx, { reviewSkipped: true, reviewedCommitSha: checkoutSha });
|
|
204665
204813
|
await bestEffort(
|
|
204666
204814
|
() => dispatchFollowUpReReview(ctx, checkoutSha),
|
|
204667
204815
|
"follow-up re-review dispatch"
|
|
@@ -204670,7 +204818,7 @@ async function postReviewCleanup(ctx) {
|
|
|
204670
204818
|
return;
|
|
204671
204819
|
}
|
|
204672
204820
|
delete ctx.toolState.review;
|
|
204673
|
-
await bestEffort(() => reportReviewNodeId(ctx,
|
|
204821
|
+
await bestEffort(() => reportReviewNodeId(ctx, review), "reportReviewNodeId");
|
|
204674
204822
|
if (review.reviewedSha) {
|
|
204675
204823
|
await bestEffort(
|
|
204676
204824
|
() => dispatchFollowUpReReview(ctx, review.reviewedSha),
|
|
@@ -205070,6 +205218,9 @@ function resolveModelForLog(ctx) {
|
|
|
205070
205218
|
function resolveEffortForLog(ctx) {
|
|
205071
205219
|
const effort = resolveRunEffort(ctx);
|
|
205072
205220
|
if (!ctx.resolvedModel && !ctx.payload.proxyModel) return "pending \u2014 model not chosen yet";
|
|
205221
|
+
if (effort.declarable && ctx.agentName === "opencode") {
|
|
205222
|
+
return "pending \u2014 reading the variants opencode declares";
|
|
205223
|
+
}
|
|
205073
205224
|
if (!effort.alias) return "not applied \u2014 model not recognized";
|
|
205074
205225
|
if (!effort.rung) return "n/a (model has no effort control)";
|
|
205075
205226
|
return effort.configured ? effort.rung : `${effort.rung} (default)`;
|
|
@@ -205088,9 +205239,7 @@ function logRunStartup(ctx) {
|
|
|
205088
205239
|
log.info(
|
|
205089
205240
|
`\xBB model: ${resolveModelForLog({ payload: ctx.payload, resolvedModel: ctx.resolvedModel })}`
|
|
205090
205241
|
);
|
|
205091
|
-
log.info(
|
|
205092
|
-
`\xBB effort: ${resolveEffortForLog({ payload: ctx.payload, resolvedModel: ctx.resolvedModel })}`
|
|
205093
|
-
);
|
|
205242
|
+
log.info(`\xBB effort: ${resolveEffortForLog(ctx)}`);
|
|
205094
205243
|
const routing = ctx.payload.routing;
|
|
205095
205244
|
if (routing) {
|
|
205096
205245
|
log.info(
|
|
@@ -205361,6 +205510,7 @@ async function main() {
|
|
|
205361
205510
|
const count = Object.keys(runContext.dbSecrets).length;
|
|
205362
205511
|
if (count > 0) log.info(`\xBB ${count} db secret(s) loaded`);
|
|
205363
205512
|
}
|
|
205513
|
+
const openAICompatibleSet = activateOpenAICompatibleSet({ slug: payload.model });
|
|
205364
205514
|
await initializeCredentialPool({ ctx: runContext, slug: payload.model, injected });
|
|
205365
205515
|
installCodexAuth();
|
|
205366
205516
|
installXaiAuth();
|
|
@@ -205509,7 +205659,8 @@ async function main() {
|
|
|
205509
205659
|
owner: runContext.repo.owner,
|
|
205510
205660
|
name: runContext.repo.name,
|
|
205511
205661
|
secretsUnavailable: runContext.secretsUnavailable,
|
|
205512
|
-
routerUnfunded: runContext.routerUnfunded
|
|
205662
|
+
routerUnfunded: runContext.routerUnfunded,
|
|
205663
|
+
openAICompatibleSet
|
|
205513
205664
|
});
|
|
205514
205665
|
} catch (missingKey) {
|
|
205515
205666
|
const funded = runContext.trialFallback && missingKey instanceof NoUsableCredentialError ? await resolveTrialFallback({
|
package/dist/internal/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export type { ConfigField, ConfigScope, ConfigValue } from "../configuration.ts"
|
|
|
7
7
|
export { configEntrySchema, configFields, configResponseSchema, configValueSchema, } from "../configuration.ts";
|
|
8
8
|
export type { AuthorPermission, AutoTier, EffortPosition, ModelAlias, ModelProvider, Payload, PayloadEvent, PayloadRouting, ProviderConfig, PushPermission, RouterSource, RouterTier, ShellPermission, ToolPermission, WriteablePayload, XrepoConfig, } from "../external.ts";
|
|
9
9
|
export { AUTO_EFFICIENT, AUTO_INTELLIGENT, AUTO_ROUTER, autoLadder, autoRoutedSlug, DEFAULT_EFFORT_POSITION, DEFAULT_PROXY_MODEL, defaultAutoTier, EFFORT_ALIASES, getAutoSelectHintModel, getModelEffortLevels, getModelEnvVars, getModelManagedCredentials, getModelProvider, getProviderDisplayName, isAutoRouted, isAutoSlug, isAutoTier, isCardGatedModel, isEffortPosition, isOssAllowedModel, isRouterTier, ladderRungs, modelAliases, modelHasStoredAuth, modelIdLabel, OSS_MODEL_ALLOWLIST, OSS_RECOMMENDED_MODEL, offeredRungs, ossFundedModelNames, PROVIDER_GATEWAY_URL_ENV, parseEffortPosition, parseModel, providers, pullfrogMcpName, ROUTED_PROVIDERS, ROUTER_LADDER, ROUTER_TIERS, resolveAutoTier, resolveCliModel, resolveDisplayAlias, resolveModelRung, resolveModelSlug, resolveOpenRouterModel, resolvePickModel, resolveRoutedModel, resolveRung, routerProxyOptOut, rungLabel, rungPosition, SUBSIDY_RUNG, TRIAL_FUNDED_USD_LIMIT, } from "../external.ts";
|
|
10
|
+
export { isOpenAICompatibleSetName, openAICompatibleEnv, openAICompatibleSetAlias, openAICompatibleSetName, openAICompatibleSetOf, openAICompatibleSets, } from "../models.ts";
|
|
10
11
|
export type { Mode } from "../modes.ts";
|
|
11
12
|
export { modes } from "../modes.ts";
|
|
12
13
|
export { type CommercialRefusal, commercialPaywallBody } from "../utils/billingErrors.ts";
|