pullfrog 0.1.88 → 0.1.90
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/dist/cli.mjs +43 -5
- package/dist/index.js +41 -3
- package/dist/internal/index.d.ts +1 -0
- package/dist/internal.js +142 -1
- package/dist/utils/runErrorRenderer.d.ts +5 -2
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -107103,7 +107103,7 @@ var import_picocolors = __toESM(require_picocolors(), 1);
|
|
|
107103
107103
|
// package.json
|
|
107104
107104
|
var package_default = {
|
|
107105
107105
|
name: "pullfrog",
|
|
107106
|
-
version: "0.1.
|
|
107106
|
+
version: "0.1.90",
|
|
107107
107107
|
cliContractVersion: "1",
|
|
107108
107108
|
type: "module",
|
|
107109
107109
|
bin: {
|
|
@@ -111130,6 +111130,11 @@ import { spawnSync as spawnSync2 } from "node:child_process";
|
|
|
111130
111130
|
import { chmodSync, createWriteStream, existsSync as existsSync3, mkdirSync } from "node:fs";
|
|
111131
111131
|
import { join as join2 } from "node:path";
|
|
111132
111132
|
import { pipeline } from "node:stream/promises";
|
|
111133
|
+
function resolveNpm() {
|
|
111134
|
+
const npmCli = process.env.npm_execpath;
|
|
111135
|
+
if (npmCli?.endsWith("npm-cli.js")) return { command: process.execPath, args: [npmCli] };
|
|
111136
|
+
return { command: "npm", args: [] };
|
|
111137
|
+
}
|
|
111133
111138
|
async function installFromNpmTarball(params) {
|
|
111134
111139
|
const tempDir = process.env.PULLFROG_TEMP_DIR;
|
|
111135
111140
|
if (!tempDir) throw new Error("PULLFROG_TEMP_DIR is not set");
|
|
@@ -111192,7 +111197,8 @@ async function installFromNpmTarball(params) {
|
|
|
111192
111197
|
}
|
|
111193
111198
|
if (params.installDependencies) {
|
|
111194
111199
|
log2.debug(`\xBB installing dependencies for ${params.packageName}...`);
|
|
111195
|
-
const
|
|
111200
|
+
const npm2 = resolveNpm();
|
|
111201
|
+
const installResult = spawnSync2(npm2.command, [...npm2.args, "install", "--production"], {
|
|
111196
111202
|
cwd: extractedDir,
|
|
111197
111203
|
stdio: "pipe",
|
|
111198
111204
|
encoding: "utf-8",
|
|
@@ -196666,8 +196672,9 @@ function ResolveReviewThreadTool(ctx) {
|
|
|
196666
196672
|
}
|
|
196667
196673
|
|
|
196668
196674
|
// mcp/searchIssues.ts
|
|
196675
|
+
var TERM = /^[\p{L}\p{N}_./-]+$/u;
|
|
196669
196676
|
var SearchIssues = type({
|
|
196670
|
-
terms: type.string.
|
|
196677
|
+
terms: type.string.atMostLength(40).array().atLeastLength(1).atMostLength(5).describe("1-5 keywords from the issue; no GitHub qualifiers or query operators")
|
|
196671
196678
|
});
|
|
196672
196679
|
function SearchIssuesTool(ctx) {
|
|
196673
196680
|
return tool({
|
|
@@ -196675,6 +196682,10 @@ function SearchIssuesTool(ctx) {
|
|
|
196675
196682
|
description: "Find open or closed issues in this repository by keyword. Inspect promising matches with get_issue before deciding whether they are duplicates or related. Results are bounded, not an exhaustive duplicate check.",
|
|
196676
196683
|
parameters: SearchIssues,
|
|
196677
196684
|
execute: execute(async (params) => {
|
|
196685
|
+
const invalid = params.terms.find((term) => !TERM.test(term));
|
|
196686
|
+
if (invalid) {
|
|
196687
|
+
throw new Error(`search term "${invalid}" may contain only letters, digits and _ . / -`);
|
|
196688
|
+
}
|
|
196678
196689
|
const repoPath = `/repos/${ctx.repo.owner}/${ctx.repo.name}`.toLowerCase();
|
|
196679
196690
|
const terms = params.terms.map((term) => `"${term}"`).join(" ");
|
|
196680
196691
|
const response = await ctx.octokit.rest.search.issuesAndPullRequests({
|
|
@@ -200098,6 +200109,23 @@ ${input.raw}
|
|
|
200098
200109
|
\`\`\``
|
|
200099
200110
|
].join("\n");
|
|
200100
200111
|
}
|
|
200112
|
+
function isRunTimeoutError(message) {
|
|
200113
|
+
return message.startsWith("agent run timed out after");
|
|
200114
|
+
}
|
|
200115
|
+
function formatRunTimeoutSummary(input) {
|
|
200116
|
+
const settingsUrl = `${getApiUrl()}/console/${input.owner}/${input.name}`;
|
|
200117
|
+
return [
|
|
200118
|
+
"**This run hit its time limit while still working, so it stopped before finishing.**",
|
|
200119
|
+
"",
|
|
200120
|
+
"Give runs longer with `--timeout=2h` (or `--notimeout`) in this repo's instructions \u2014 or raise the workflow's `timeout` input if it sets one, since that input wins. A smaller PR or a lower effort also finishes sooner.",
|
|
200121
|
+
"",
|
|
200122
|
+
`[Repo settings \u2192](${settingsUrl}) \xB7 [Flags \u2192](https://docs.pullfrog.com/flags)`,
|
|
200123
|
+
"",
|
|
200124
|
+
`\`\`\`
|
|
200125
|
+
${input.raw}
|
|
200126
|
+
\`\`\``
|
|
200127
|
+
].join("\n");
|
|
200128
|
+
}
|
|
200101
200129
|
function formatContextOverflowSummary(input) {
|
|
200102
200130
|
const settingsUrl = `${getApiUrl()}/console/${input.owner}/${input.name}`;
|
|
200103
200131
|
return [
|
|
@@ -200347,6 +200375,16 @@ ${body}`, comment: body };
|
|
|
200347
200375
|
});
|
|
200348
200376
|
return { summary: `### \u274C Pullfrog failed
|
|
200349
200377
|
|
|
200378
|
+
${body}`, comment: body };
|
|
200379
|
+
}
|
|
200380
|
+
if (isRunTimeoutError(input.errorMessage)) {
|
|
200381
|
+
const body = formatRunTimeoutSummary({
|
|
200382
|
+
owner: input.repo.owner,
|
|
200383
|
+
name: input.repo.name,
|
|
200384
|
+
raw: input.errorMessage
|
|
200385
|
+
});
|
|
200386
|
+
return { summary: `### \u274C Pullfrog failed
|
|
200387
|
+
|
|
200350
200388
|
${body}`, comment: body };
|
|
200351
200389
|
}
|
|
200352
200390
|
if (isTransientUpstreamError(input.errorMessage)) {
|
|
@@ -202192,7 +202230,7 @@ function resolveTarget2(repo) {
|
|
|
202192
202230
|
return { owner: match3[1], repo: match3[2] };
|
|
202193
202231
|
}
|
|
202194
202232
|
function cliVersion() {
|
|
202195
|
-
const parts = /^(\d+)\.(\d+)\.(\d+)$/.exec("0.1.
|
|
202233
|
+
const parts = /^(\d+)\.(\d+)\.(\d+)$/.exec("0.1.90");
|
|
202196
202234
|
if (!parts) return "0.0.0";
|
|
202197
202235
|
return `${Number(parts[1])}.${Number(parts[2])}.${Number(parts[3])}`;
|
|
202198
202236
|
}
|
|
@@ -202359,7 +202397,7 @@ async function runCli8(input) {
|
|
|
202359
202397
|
}
|
|
202360
202398
|
|
|
202361
202399
|
// cli.ts
|
|
202362
|
-
var VERSION10 = "0.1.
|
|
202400
|
+
var VERSION10 = "0.1.90";
|
|
202363
202401
|
var bin = basename2(process.argv[1] || "");
|
|
202364
202402
|
var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
|
|
202365
202403
|
var rawArgs = process.argv.slice(2);
|
package/dist/index.js
CHANGED
|
@@ -105416,6 +105416,11 @@ import { spawnSync } from "node:child_process";
|
|
|
105416
105416
|
import { chmodSync, createWriteStream, existsSync as existsSync2, mkdirSync } from "node:fs";
|
|
105417
105417
|
import { join } from "node:path";
|
|
105418
105418
|
import { pipeline } from "node:stream/promises";
|
|
105419
|
+
function resolveNpm() {
|
|
105420
|
+
const npmCli = process.env.npm_execpath;
|
|
105421
|
+
if (npmCli?.endsWith("npm-cli.js")) return { command: process.execPath, args: [npmCli] };
|
|
105422
|
+
return { command: "npm", args: [] };
|
|
105423
|
+
}
|
|
105419
105424
|
async function installFromNpmTarball(params) {
|
|
105420
105425
|
const tempDir = process.env.PULLFROG_TEMP_DIR;
|
|
105421
105426
|
if (!tempDir) throw new Error("PULLFROG_TEMP_DIR is not set");
|
|
@@ -105478,7 +105483,8 @@ async function installFromNpmTarball(params) {
|
|
|
105478
105483
|
}
|
|
105479
105484
|
if (params.installDependencies) {
|
|
105480
105485
|
log.debug(`\xBB installing dependencies for ${params.packageName}...`);
|
|
105481
|
-
const
|
|
105486
|
+
const npm2 = resolveNpm();
|
|
105487
|
+
const installResult = spawnSync(npm2.command, [...npm2.args, "install", "--production"], {
|
|
105482
105488
|
cwd: extractedDir,
|
|
105483
105489
|
stdio: "pipe",
|
|
105484
105490
|
encoding: "utf-8",
|
|
@@ -105723,7 +105729,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
105723
105729
|
// package.json
|
|
105724
105730
|
var package_default = {
|
|
105725
105731
|
name: "pullfrog",
|
|
105726
|
-
version: "0.1.
|
|
105732
|
+
version: "0.1.90",
|
|
105727
105733
|
cliContractVersion: "1",
|
|
105728
105734
|
type: "module",
|
|
105729
105735
|
bin: {
|
|
@@ -193159,8 +193165,9 @@ function ResolveReviewThreadTool(ctx) {
|
|
|
193159
193165
|
}
|
|
193160
193166
|
|
|
193161
193167
|
// mcp/searchIssues.ts
|
|
193168
|
+
var TERM = /^[\p{L}\p{N}_./-]+$/u;
|
|
193162
193169
|
var SearchIssues = type({
|
|
193163
|
-
terms: type.string.
|
|
193170
|
+
terms: type.string.atMostLength(40).array().atLeastLength(1).atMostLength(5).describe("1-5 keywords from the issue; no GitHub qualifiers or query operators")
|
|
193164
193171
|
});
|
|
193165
193172
|
function SearchIssuesTool(ctx) {
|
|
193166
193173
|
return tool({
|
|
@@ -193168,6 +193175,10 @@ function SearchIssuesTool(ctx) {
|
|
|
193168
193175
|
description: "Find open or closed issues in this repository by keyword. Inspect promising matches with get_issue before deciding whether they are duplicates or related. Results are bounded, not an exhaustive duplicate check.",
|
|
193169
193176
|
parameters: SearchIssues,
|
|
193170
193177
|
execute: execute(async (params) => {
|
|
193178
|
+
const invalid = params.terms.find((term) => !TERM.test(term));
|
|
193179
|
+
if (invalid) {
|
|
193180
|
+
throw new Error(`search term "${invalid}" may contain only letters, digits and _ . / -`);
|
|
193181
|
+
}
|
|
193171
193182
|
const repoPath = `/repos/${ctx.repo.owner}/${ctx.repo.name}`.toLowerCase();
|
|
193172
193183
|
const terms = params.terms.map((term) => `"${term}"`).join(" ");
|
|
193173
193184
|
const response = await ctx.octokit.rest.search.issuesAndPullRequests({
|
|
@@ -196591,6 +196602,23 @@ ${input.raw}
|
|
|
196591
196602
|
\`\`\``
|
|
196592
196603
|
].join("\n");
|
|
196593
196604
|
}
|
|
196605
|
+
function isRunTimeoutError(message) {
|
|
196606
|
+
return message.startsWith("agent run timed out after");
|
|
196607
|
+
}
|
|
196608
|
+
function formatRunTimeoutSummary(input) {
|
|
196609
|
+
const settingsUrl = `${getApiUrl()}/console/${input.owner}/${input.name}`;
|
|
196610
|
+
return [
|
|
196611
|
+
"**This run hit its time limit while still working, so it stopped before finishing.**",
|
|
196612
|
+
"",
|
|
196613
|
+
"Give runs longer with `--timeout=2h` (or `--notimeout`) in this repo's instructions \u2014 or raise the workflow's `timeout` input if it sets one, since that input wins. A smaller PR or a lower effort also finishes sooner.",
|
|
196614
|
+
"",
|
|
196615
|
+
`[Repo settings \u2192](${settingsUrl}) \xB7 [Flags \u2192](https://docs.pullfrog.com/flags)`,
|
|
196616
|
+
"",
|
|
196617
|
+
`\`\`\`
|
|
196618
|
+
${input.raw}
|
|
196619
|
+
\`\`\``
|
|
196620
|
+
].join("\n");
|
|
196621
|
+
}
|
|
196594
196622
|
function formatContextOverflowSummary(input) {
|
|
196595
196623
|
const settingsUrl = `${getApiUrl()}/console/${input.owner}/${input.name}`;
|
|
196596
196624
|
return [
|
|
@@ -196840,6 +196868,16 @@ ${body}`, comment: body };
|
|
|
196840
196868
|
});
|
|
196841
196869
|
return { summary: `### \u274C Pullfrog failed
|
|
196842
196870
|
|
|
196871
|
+
${body}`, comment: body };
|
|
196872
|
+
}
|
|
196873
|
+
if (isRunTimeoutError(input.errorMessage)) {
|
|
196874
|
+
const body = formatRunTimeoutSummary({
|
|
196875
|
+
owner: input.repo.owner,
|
|
196876
|
+
name: input.repo.name,
|
|
196877
|
+
raw: input.errorMessage
|
|
196878
|
+
});
|
|
196879
|
+
return { summary: `### \u274C Pullfrog failed
|
|
196880
|
+
|
|
196843
196881
|
${body}`, comment: body };
|
|
196844
196882
|
}
|
|
196845
196883
|
if (isTransientUpstreamError(input.errorMessage)) {
|
package/dist/internal/index.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export { countOutstandingPullfrogThreads } from "../utils/outstandingThreads.ts"
|
|
|
24
24
|
export type { CreateProgressCommentTarget, ProgressComment, ProgressCommentType, } from "../utils/progressComment.ts";
|
|
25
25
|
export { createLeapingProgressComment, deleteProgressCommentApi, getProgressComment, updateProgressComment, } from "../utils/progressComment.ts";
|
|
26
26
|
export { PROVIDER_DASHBOARDS, type ProviderDashboard } from "../utils/providerDashboards.ts";
|
|
27
|
+
export { detectProviderError } from "../utils/providerErrors.ts";
|
|
27
28
|
export type { RunStatusCheckConclusion, RunStatusCheckOctokit, } from "../utils/runStatusCheck.ts";
|
|
28
29
|
export { APPROVAL_CHECK_NAME, createRunStatusCheck, finalizeRunStatusCheck, RUN_STATUS_CHECK_NAME, runStatusCheckNeedsFinalizing, } from "../utils/runStatusCheck.ts";
|
|
29
30
|
export * from "../utils/subscriptionCredentials.ts";
|
package/dist/internal.js
CHANGED
|
@@ -8,7 +8,7 @@ var __export = (target, all) => {
|
|
|
8
8
|
// package.json
|
|
9
9
|
var package_default = {
|
|
10
10
|
name: "pullfrog",
|
|
11
|
-
version: "0.1.
|
|
11
|
+
version: "0.1.90",
|
|
12
12
|
cliContractVersion: "1",
|
|
13
13
|
type: "module",
|
|
14
14
|
bin: {
|
|
@@ -16821,6 +16821,146 @@ var PROVIDER_DASHBOARDS = {
|
|
|
16821
16821
|
}
|
|
16822
16822
|
};
|
|
16823
16823
|
|
|
16824
|
+
// utils/providerErrors.ts
|
|
16825
|
+
var PROVIDER_BILLING_EXHAUSTED_LABEL = "provider billing exhausted";
|
|
16826
|
+
var PROVIDER_NO_ENDPOINTS_LABEL = "provider no routable endpoints";
|
|
16827
|
+
var PROVIDER_USAGE_LIMIT_LABEL = "provider usage limit reached";
|
|
16828
|
+
var statusKey = `\\b(?:status[_ ]?code|http[_ ]?status|status)["']?\\s*[:=]\\s*["']?`;
|
|
16829
|
+
var ANTHROPIC_SPEND_CAP_PATTERN = /reached your specified API usage limits\.?(?:\s*You will regain access on ([^"\n]+?)\.)?/i;
|
|
16830
|
+
var PROVIDER_ERROR_PATTERNS = [
|
|
16831
|
+
// billing-payload patterns come BEFORE bare status-code patterns. providers
|
|
16832
|
+
// commonly return 401 / 429 for billing/quota exhaustion (OpenCode Zen
|
|
16833
|
+
// `CreditsError` / `FreeUsageLimitError`, Gemini `RESOURCE_EXHAUSTED` +
|
|
16834
|
+
// "spending cap", Anthropic "Insufficient balance" / "credit balance is
|
|
16835
|
+
// too low"). these are non-retryable and require user-billing action —
|
|
16836
|
+
// distinct from a transient auth error or rate-limit. status-code patterns
|
|
16837
|
+
// would otherwise win and surface "auth error (401)" / "rate limited (429)"
|
|
16838
|
+
// with no billing hint. see #778, #835.
|
|
16839
|
+
{ regex: /\bCreditsError\b/, label: PROVIDER_BILLING_EXHAUSTED_LABEL },
|
|
16840
|
+
{ regex: /\bFreeUsageLimitError\b/, label: PROVIDER_BILLING_EXHAUSTED_LABEL },
|
|
16841
|
+
// `balance` is DeepSeek's noun, `credits` is OpenRouter's for the SAME
|
|
16842
|
+
// terminal state — #1164's run 7 (`Insufficient credits. Add more using
|
|
16843
|
+
// …/settings/credits`) matched neither this nor `requires more credits`, so
|
|
16844
|
+
// the one run in that episode that was a genuinely empty wallet rendered with
|
|
16845
|
+
// no CTA at all while runs 1-6 rendered correctly.
|
|
16846
|
+
{ regex: /Insufficient (?:balance|credits)/i, label: PROVIDER_BILLING_EXHAUSTED_LABEL },
|
|
16847
|
+
{ regex: /credit balance is too low/i, label: PROVIDER_BILLING_EXHAUSTED_LABEL },
|
|
16848
|
+
// "spending cap" (Gemini) and "spending limit" (xAI, #1076) are the same
|
|
16849
|
+
// condition in two house styles — a configured ceiling was reached.
|
|
16850
|
+
{ regex: /spending (?:cap|limit)/i, label: PROVIDER_BILLING_EXHAUSTED_LABEL },
|
|
16851
|
+
// xAI exhausts credits and the monthly ceiling in one 403 whose status code
|
|
16852
|
+
// sits nowhere near a `status:` key, so no status pattern below fires (#1076).
|
|
16853
|
+
{ regex: /used all available credits/i, label: PROVIDER_BILLING_EXHAUSTED_LABEL },
|
|
16854
|
+
// Gemini's daily / free-tier cap. it tells the user to "check your plan and
|
|
16855
|
+
// billing details" in its own words but matches none of the wallet wordings
|
|
16856
|
+
// above, so it fell to the catch-all `quota` label — which is not the label
|
|
16857
|
+
// the actionable copy is keyed on, so the run rendered a blank comment for a
|
|
16858
|
+
// condition the provider had spelled out (#1114).
|
|
16859
|
+
{
|
|
16860
|
+
regex: /exceeded your current quota, please check your plan and billing/i,
|
|
16861
|
+
label: PROVIDER_BILLING_EXHAUSTED_LABEL
|
|
16862
|
+
},
|
|
16863
|
+
{ regex: /generate_content_free_tier_requests/i, label: PROVIDER_BILLING_EXHAUSTED_LABEL },
|
|
16864
|
+
// Anthropic's Console spend cap. its noun is "API usage limits", which none of
|
|
16865
|
+
// the wallet-shaped patterns above reach, and its 400 sits nowhere near a
|
|
16866
|
+
// `status:` key either — so the whole message was dropped on the floor (#1208).
|
|
16867
|
+
{ regex: ANTHROPIC_SPEND_CAP_PATTERN, label: PROVIDER_BILLING_EXHAUSTED_LABEL },
|
|
16868
|
+
// OpenCode Zen / Codex-subscription cap: `AI_APICallError: The usage limit has
|
|
16869
|
+
// been reached`. Its noun is "usage limit" with no "rate", no status code and
|
|
16870
|
+
// no wallet wording, so every pattern here missed it and `lastProviderError`
|
|
16871
|
+
// stayed unset — which suppresses nothing, so the run was reported as "The
|
|
16872
|
+
// model produced no output at all … usually transient; re-running often
|
|
16873
|
+
// succeeds" while the same log held nine of these. Measured on a live account
|
|
16874
|
+
// whose ChatGPT subscription was capped.
|
|
16875
|
+
{ regex: /\bthe usage limit has been reached\b/i, label: PROVIDER_USAGE_LIMIT_LABEL },
|
|
16876
|
+
// the OpenRouter shapes `isRouterKeylimitExhaustedError` matches. on a Router
|
|
16877
|
+
// run those mean the PULLFROG wallet is empty and `renderRunError` returns a
|
|
16878
|
+
// `BillingError` before ever reaching this list; on a BYOK run the very same
|
|
16879
|
+
// wire text means the user's OWN OpenRouter wallet is empty, and this entry is
|
|
16880
|
+
// what gives them the right dashboard instead of ours. see #1135.
|
|
16881
|
+
{
|
|
16882
|
+
regex: /requires more credits|Key limit exceeded \(total limit\)/i,
|
|
16883
|
+
label: PROVIDER_BILLING_EXHAUSTED_LABEL
|
|
16884
|
+
},
|
|
16885
|
+
// OpenRouter has no endpoint it is allowed to route to: the customer's
|
|
16886
|
+
// data-policy / guardrail settings exclude every provider serving the model.
|
|
16887
|
+
// dies ~2s into the session with nothing billed, so it is neither a billing
|
|
16888
|
+
// nor an auth condition — and both of those patterns would otherwise be
|
|
16889
|
+
// reached first for a message that mentions neither. see #1164.
|
|
16890
|
+
{
|
|
16891
|
+
regex: /No endpoints available matching your (?:guardrail restrictions|data policy)/i,
|
|
16892
|
+
label: PROVIDER_NO_ENDPOINTS_LABEL
|
|
16893
|
+
},
|
|
16894
|
+
// auth patterns must come BEFORE rate-limit patterns. OpenRouter 401 error
|
|
16895
|
+
// payloads carry `x-ratelimit-*` response headers in the dump, and the
|
|
16896
|
+
// free-form rate-limit regex below would otherwise win on word-boundary
|
|
16897
|
+
// matches inside header names. canonical 401 messages: OpenRouter returns
|
|
16898
|
+
// `{"error":{"message":"User not found","code":401}}` for disabled or
|
|
16899
|
+
// invalid keys (https://openai.luzhipeng.com/docs/api/reference/errors-and-debugging).
|
|
16900
|
+
{ regex: new RegExp(`${statusKey}401\\b`, "i"), label: "auth error (401)" },
|
|
16901
|
+
{ regex: new RegExp(`${statusKey}403\\b`, "i"), label: "auth error (403)" },
|
|
16902
|
+
{ regex: /\bUser not found\b/i, label: "auth error (invalid/disabled key)" },
|
|
16903
|
+
{ regex: /\bInvalid authentication\b/i, label: "auth error (invalid credentials)" },
|
|
16904
|
+
{ regex: /\bNo auth credentials found\b/i, label: "auth error (missing credentials)" },
|
|
16905
|
+
{ regex: new RegExp(`${statusKey}429\\b`, "i"), label: "rate limited (429)" },
|
|
16906
|
+
{ regex: new RegExp(`${statusKey}500\\b`, "i"), label: "provider 500 error" },
|
|
16907
|
+
{ regex: new RegExp(`${statusKey}503\\b`, "i"), label: "provider unavailable (503)" },
|
|
16908
|
+
// matches `rate limit`, `rate limited`, `rate limits exceeded`,
|
|
16909
|
+
// `rate_limit_error`, `rate_limit_exceeded`. the leading `\b` + `[_ ]`
|
|
16910
|
+
// separator rejects `x-ratelimit-*` / `anthropic-ratelimit-*` response
|
|
16911
|
+
// headers (no separator between "rate" and "limit") which routinely
|
|
16912
|
+
// appear in dumped 401 / 4xx error JSON.
|
|
16913
|
+
{ regex: /\brate[_ ]limit/i, label: "rate limited" },
|
|
16914
|
+
{ regex: /\bRESOURCE_EXHAUSTED\b/, label: "quota exhausted" },
|
|
16915
|
+
// Google gRPC `INTERNAL` status. word-boundary anchors reject
|
|
16916
|
+
// `INTERNAL_SERVER_ERROR` (HTTP 500 message that may appear in unrelated
|
|
16917
|
+
// log lines) and identifiers like `INTERNALS`.
|
|
16918
|
+
{ regex: /\bINTERNAL\b/, label: "provider internal error" },
|
|
16919
|
+
{ regex: /\bUNAVAILABLE\b/, label: "provider unavailable" },
|
|
16920
|
+
// matches `quota`, `insufficient_quota`, `quota_exceeded`, `quotaExceeded`.
|
|
16921
|
+
// word-character lookarounds would reject `_quota` / `quotaX`; `quota` is
|
|
16922
|
+
// specific enough that a plain substring match is safe.
|
|
16923
|
+
{ regex: /quota/i, label: "quota error" },
|
|
16924
|
+
// explicit zero-quota response, e.g. `{"limit": 0}`. the `\b` anchor
|
|
16925
|
+
// around `limit` rejects keys like `time_limit` or `field_limit`.
|
|
16926
|
+
{ regex: /["']?\blimit\b["']?\s*:\s*0\b/, label: "zero quota" }
|
|
16927
|
+
];
|
|
16928
|
+
var EXCERPT_MAX_BYTES = 600;
|
|
16929
|
+
var LINES_BEFORE = 1;
|
|
16930
|
+
var LINES_AFTER = 2;
|
|
16931
|
+
function findProviderErrorMatch(text) {
|
|
16932
|
+
for (const entry of PROVIDER_ERROR_PATTERNS) {
|
|
16933
|
+
const m = entry.regex.exec(text);
|
|
16934
|
+
if (!m) continue;
|
|
16935
|
+
return { label: entry.label, excerpt: extractExcerpt(text, m.index) };
|
|
16936
|
+
}
|
|
16937
|
+
return null;
|
|
16938
|
+
}
|
|
16939
|
+
function detectProviderError(text) {
|
|
16940
|
+
return findProviderErrorMatch(text)?.label ?? null;
|
|
16941
|
+
}
|
|
16942
|
+
function extractExcerpt(text, matchIndex) {
|
|
16943
|
+
const lineStart = text.lastIndexOf("\n", matchIndex - 1) + 1;
|
|
16944
|
+
const lineEndRaw = text.indexOf("\n", matchIndex);
|
|
16945
|
+
const lineEnd = lineEndRaw === -1 ? text.length : lineEndRaw;
|
|
16946
|
+
let start = lineStart;
|
|
16947
|
+
for (let i = 0; i < LINES_BEFORE && start > 0; i++) {
|
|
16948
|
+
const prev = text.lastIndexOf("\n", start - 2);
|
|
16949
|
+
start = prev < 0 ? 0 : prev + 1;
|
|
16950
|
+
}
|
|
16951
|
+
let end = lineEnd;
|
|
16952
|
+
for (let i = 0; i < LINES_AFTER && end < text.length; i++) {
|
|
16953
|
+
const next = text.indexOf("\n", end + 1);
|
|
16954
|
+
end = next < 0 ? text.length : next;
|
|
16955
|
+
}
|
|
16956
|
+
let excerpt = text.slice(start, end);
|
|
16957
|
+
if (excerpt.length > EXCERPT_MAX_BYTES) {
|
|
16958
|
+
excerpt = text.slice(lineStart, lineEnd);
|
|
16959
|
+
if (excerpt.length > EXCERPT_MAX_BYTES) excerpt = excerpt.slice(0, EXCERPT_MAX_BYTES);
|
|
16960
|
+
}
|
|
16961
|
+
return excerpt.trim();
|
|
16962
|
+
}
|
|
16963
|
+
|
|
16824
16964
|
// utils/runStatusCheck.ts
|
|
16825
16965
|
var RUN_STATUS_CHECK_NAME = "pullfrog";
|
|
16826
16966
|
var GITHUB_ACTIONS_APP_SLUG = "github-actions";
|
|
@@ -17214,6 +17354,7 @@ export {
|
|
|
17214
17354
|
decodeJwtExpMs,
|
|
17215
17355
|
defaultAutoTier,
|
|
17216
17356
|
deleteProgressCommentApi,
|
|
17357
|
+
detectProviderError,
|
|
17217
17358
|
finalizeRunStatusCheck,
|
|
17218
17359
|
getAutoSelectHintModel,
|
|
17219
17360
|
getModelEffortLevels,
|
|
@@ -40,6 +40,9 @@
|
|
|
40
40
|
* `maximum context length is N tokens`. Actionable, so it renders on
|
|
41
41
|
* both surfaces rather than collapsing to the one-line comment.
|
|
42
42
|
*
|
|
43
|
+
* 4b'. Run time limit — the agent was still working when the run's own
|
|
44
|
+
* timeout fired. Actionable (raise the limit), so both surfaces.
|
|
45
|
+
*
|
|
43
46
|
* 4c. Transient upstream failure (#1173) — Anthropic `529 Overloaded`,
|
|
44
47
|
* OpenRouter `provider_unavailable` / `timeout`, Zen `Streaming response
|
|
45
48
|
* failed: [5xx]`. Last of the classified branches so every more specific
|
|
@@ -63,8 +66,8 @@
|
|
|
63
66
|
* the hang case, since the raw internal string helps nobody on the PR.
|
|
64
67
|
*
|
|
65
68
|
* Net: the actionable classifications (billing, API-key, model-not-found,
|
|
66
|
-
* no-provider-available, context-overflow, transient-upstream)
|
|
67
|
-
* bodies on both surfaces; the non-actionable ones (unexplained hang,
|
|
69
|
+
* no-provider-available, context-overflow, run time limit, transient-upstream)
|
|
70
|
+
* render identical bodies on both surfaces; the non-actionable ones (unexplained hang,
|
|
68
71
|
* generic) keep the forensics in the Actions job summary and show a calm
|
|
69
72
|
* one-liner in the PR comment, whose footer already carries Pullfrog
|
|
70
73
|
* branding + rerun links.
|