pullfrog 0.1.52 → 0.1.54

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.
@@ -3,7 +3,7 @@
3
3
  * Re-exports shared types, values, and utilities needed by the Next.js app.
4
4
  */
5
5
  export type { AuthorPermission, AutoTier, EffortPosition, ModelAlias, ModelProvider, Payload, PayloadEvent, ProviderConfig, PushPermission, ShellPermission, ToolPermission, WriteablePayload, XrepoConfig, } from "../external.ts";
6
- export { AUTO_EFFICIENT, AUTO_INTELLIGENT, DEFAULT_EFFORT_POSITION, DEFAULT_PROXY_MODEL, defaultAutoTier, EFFORT_ALIASES, getAutoSelectHintModel, getModelEffortLevels, getModelEnvVars, getModelManagedCredentials, getModelProvider, getProviderDisplayName, isAutoTier, isCardGatedModel, isEffortPosition, isOssAllowedModel, modelAliases, modelHasStoredAuth, OSS_MODEL_ALLOWLIST, OSS_RECOMMENDED_MODEL, offeredRungs, parseEffortPosition, parseModel, providers, pullfrogMcpName, resolveAutoTier, resolveCliModel, resolveDisplayAlias, resolveModelRung, resolveModelSlug, resolveOpenRouterModel, resolveRung, rungLabel, rungPosition, } from "../external.ts";
6
+ export { AUTO_EFFICIENT, AUTO_INTELLIGENT, DEFAULT_EFFORT_POSITION, DEFAULT_PROXY_MODEL, defaultAutoTier, EFFORT_ALIASES, getAutoSelectHintModel, getModelEffortLevels, getModelEnvVars, getModelManagedCredentials, getModelProvider, getProviderDisplayName, isAutoTier, isCardGatedModel, isEffortPosition, isOssAllowedModel, modelAliases, modelHasStoredAuth, OSS_MODEL_ALLOWLIST, OSS_RECOMMENDED_MODEL, offeredRungs, ossFundedModelNames, parseEffortPosition, parseModel, providers, pullfrogMcpName, resolveAutoTier, resolveCliModel, resolveDisplayAlias, resolveModelRung, resolveModelSlug, resolveOpenRouterModel, resolveRung, rungLabel, rungPosition, } from "../external.ts";
7
7
  export type { Mode } from "../modes.ts";
8
8
  export { modes } from "../modes.ts";
9
9
  export { commercialPaywallBody } from "../utils/billingErrors.ts";
@@ -11,6 +11,8 @@ export type { BuildPullfrogFooterParams, WorkflowRunFooterInfo, } from "../utils
11
11
  export { buildPullfrogFooter, PULLFROG_DIVIDER, stripExistingFooter, } from "../utils/buildPullfrogFooter.ts";
12
12
  export type { CodexAuthBody } from "../utils/codexOAuth.ts";
13
13
  export { decodeJwtExpMs, OAuthInvalidGrantError, parseCodexAuthBody, refreshCodexAuthBody, stringifyCodexAuthBody, } from "../utils/codexOAuth.ts";
14
+ export type { CredentialVerdict } from "../utils/credentialCheck.ts";
15
+ export { isCredentialProbeable, verifyCredential } from "../utils/credentialCheck.ts";
14
16
  export type { ResourceUsage, UsageSummary } from "../utils/github.ts";
15
17
  export { isPullfrog } from "../utils/isPullfrog.ts";
16
18
  export { isLeapingIntoActionCommentBody, LEAPING_INTO_ACTION_PREFIX, } from "../utils/leapingComment.ts";
package/dist/internal.js CHANGED
@@ -207,9 +207,9 @@ var providers = {
207
207
  },
208
208
  "gemini-flash": {
209
209
  displayName: "Gemini Flash",
210
- resolve: "google/gemini-3.5-flash",
210
+ resolve: "google/gemini-3.6-flash",
211
211
  effort: ["minimal", "low", "medium", "high"],
212
- openRouterResolve: "openrouter/google/gemini-3.5-flash"
212
+ openRouterResolve: "openrouter/google/gemini-3.6-flash"
213
213
  }
214
214
  }
215
215
  }),
@@ -219,26 +219,26 @@ var providers = {
219
219
  models: {
220
220
  grok: {
221
221
  displayName: "Grok",
222
- resolve: "xai/grok-4.3",
223
- effort: ["none", "low", "medium", "high"],
224
- openRouterResolve: "openrouter/x-ai/grok-4.3",
222
+ resolve: "xai/grok-4.5",
223
+ effort: ["low", "medium", "high"],
224
+ openRouterResolve: "openrouter/x-ai/grok-4.5",
225
225
  preferred: true
226
226
  },
227
227
  // legacy aliases — xAI retired the entire fast/code-fast line on
228
228
  // 2026-05-15 (https://docs.x.ai/developers/migration/may-15-deprecation)
229
229
  // and now redirects every deprecated text-model slug to grok-4.3 at
230
230
  // standard pricing. fall back to the live `xai/grok` so the alias
231
- // chain resolves to grok-4.3 for both direct-key and OpenRouter users.
231
+ // chain resolves to grok-4.5 for both direct-key and OpenRouter users.
232
232
  "grok-fast": {
233
233
  displayName: "Grok Fast",
234
234
  resolve: "xai/grok-4-1-fast",
235
- openRouterResolve: "openrouter/x-ai/grok-4.3",
235
+ openRouterResolve: "openrouter/x-ai/grok-4.5",
236
236
  fallback: "xai/grok"
237
237
  },
238
238
  "grok-code-fast": {
239
239
  displayName: "Grok Code Fast",
240
240
  resolve: "xai/grok-code-fast-1",
241
- openRouterResolve: "openrouter/x-ai/grok-4.3",
241
+ openRouterResolve: "openrouter/x-ai/grok-4.5",
242
242
  fallback: "xai/grok"
243
243
  }
244
244
  }
@@ -255,20 +255,25 @@ var providers = {
255
255
  openRouterResolve: "openrouter/deepseek/deepseek-v4-pro",
256
256
  preferred: true
257
257
  },
258
- // DeepSeek upgraded the `deepseek-v4-flash` API model in place to the
259
- // 0731 release, but OpenRouter forked it into a separate `-0731` id and
260
- // left the April preview live at a HIGHER price — so the direct
261
- // `resolve` needs no version and the OpenRouter route must be pinned.
262
- // 0731 publishes a DIFFERENT OpenRouter ladder to both the April preview
263
- // and Pro (`high, xhigh`) it gained a `low` rung and renamed the top to
264
- // `max`. that makes `low`, not `high`, position 0 on this route, which is
258
+ // DeepSeek upgrades the `deepseek-v4-flash` API model in place, so the
259
+ // direct `resolve` needs no version. OpenRouter instead forked the 0731
260
+ // release into its own id and left the April preview live under the
261
+ // unversioned one at a HIGHER price ($0.14/$0.28 vs $0.09/$0.18) so the
262
+ // OpenRouter route can never use the bare id. it tracks the rolling
263
+ // `~deepseek/*-latest` pointer (rule 4), which resolves to 0731 at the
264
+ // same price today and follows the next in-place upgrade on its own,
265
+ // rather than pinning a dated snapshot that goes stale every release.
266
+ // 0731 gained a `low` rung and renamed the top to `max`, unlike Pro
267
+ // (`high, max` direct / `high, xhigh` via OpenRouter). that ladder now
268
+ // reads the same on BOTH routes — the in-place direct upgrade caught up
269
+ // to the fork — so `low`, not `high`, is position 0 either way, which is
265
270
  // why the OSS effort floor in `main.ts` pins the `high` rung by name.
266
271
  "deepseek-flash": {
267
272
  displayName: "DeepSeek Flash",
268
273
  resolve: "deepseek/deepseek-v4-flash",
269
- effort: ["high", "max"],
274
+ effort: ["low", "high", "max"],
270
275
  openRouterEffort: ["low", "high", "max"],
271
- openRouterResolve: "openrouter/deepseek/deepseek-v4-flash-0731"
276
+ openRouterResolve: "openrouter/~deepseek/deepseek-v4-flash-latest"
272
277
  },
273
278
  // legacy aliases — deepseek retires these on 2026-07-24; transparently
274
279
  // upgrade existing users to the v4 family via the fallback chain.
@@ -316,7 +321,12 @@ var providers = {
316
321
  displayName: "Big Pickle",
317
322
  resolve: "opencode/big-pickle",
318
323
  preferred: true,
319
- envVars: [],
324
+ // free to RUN, but Zen still refuses it without a key — a keyless run
325
+ // dies at session start with `No provider available`. the `envVars: []`
326
+ // override that used to sit here shadowed the provider's own
327
+ // `OPENCODE_API_KEY` and made every static gate wave it through (#1077).
328
+ // `isFree` stays: it is what skips the proxy mint in run-context, and
329
+ // the model does cost nothing.
320
330
  isFree: true
321
331
  },
322
332
  "claude-opus": {
@@ -414,21 +424,19 @@ var providers = {
414
424
  },
415
425
  "gemini-flash": {
416
426
  displayName: "Gemini Flash",
417
- resolve: "opencode/gemini-3.5-flash",
427
+ resolve: "opencode/gemini-3.6-flash",
418
428
  effort: ["minimal", "low", "medium", "high"],
419
- openRouterResolve: "openrouter/google/gemini-3.5-flash"
429
+ openRouterResolve: "openrouter/google/gemini-3.6-flash"
420
430
  },
421
431
  "kimi-k2": {
422
432
  displayName: "Kimi K2",
423
- // opencode Zen serves only up to k2.6; the OpenRouter fallback (used for
424
- // router/oss proxy runs) takes the newer k2.7-code.
425
- resolve: "opencode/kimi-k2.6",
433
+ resolve: "opencode/kimi-k2.7-code",
426
434
  openRouterResolve: "openrouter/moonshotai/kimi-k2.7-code"
427
435
  },
428
- // Zen serves m2.5; the funded OpenRouter path tracks the current m2.7.
436
+ // slug pins the m2 line for DB stability; resolve tracks the current m2.7.
429
437
  "minimax-m2.5": {
430
438
  displayName: "MiniMax M2",
431
- resolve: "opencode/minimax-m2.5",
439
+ resolve: "opencode/minimax-m2.7",
432
440
  openRouterResolve: "openrouter/minimax/minimax-m2.7"
433
441
  },
434
442
  "gpt-5-nano": {
@@ -440,14 +448,16 @@ var providers = {
440
448
  "mimo-v2-pro-free": {
441
449
  displayName: "MiMo V2 Pro",
442
450
  resolve: "opencode/mimo-v2-pro-free",
443
- envVars: [],
451
+ // free to run, still gated on the provider's own OPENCODE_API_KEY —
452
+ // see the big-pickle note above (#1077).
444
453
  isFree: true,
445
454
  fallback: "opencode/big-pickle"
446
455
  },
447
456
  "minimax-m2.5-free": {
448
457
  displayName: "MiniMax M2",
449
458
  resolve: "opencode/minimax-m2.5-free",
450
- envVars: [],
459
+ // free to run, still gated on the provider's own OPENCODE_API_KEY —
460
+ // see the big-pickle note above (#1077).
451
461
  isFree: true,
452
462
  fallback: "opencode/big-pickle",
453
463
  hidden: true
@@ -643,14 +653,18 @@ var providers = {
643
653
  "gemini-flash": {
644
654
  displayName: "Gemini Flash",
645
655
  resolve: "openrouter/~google/gemini-flash-latest",
646
- effort: ["minimal", "low", "medium", "high"],
656
+ // no `minimal` here, unlike the google/ and opencode/ entries for the
657
+ // same model: OpenRouter's floating `~google/gemini-flash-latest` stopped
658
+ // publishing that rung. mirroring it is not cosmetic — claude-code
659
+ // hard-errors on an out-of-range `--effort` before it calls the API.
660
+ effort: ["low", "medium", "high"],
647
661
  openRouterResolve: "openrouter/~google/gemini-flash-latest"
648
662
  },
649
663
  grok: {
650
664
  displayName: "Grok",
651
- resolve: "openrouter/x-ai/grok-4.3",
652
- effort: ["none", "low", "medium", "high"],
653
- openRouterResolve: "openrouter/x-ai/grok-4.3"
665
+ resolve: "openrouter/x-ai/grok-4.5",
666
+ effort: ["low", "medium", "high"],
667
+ openRouterResolve: "openrouter/x-ai/grok-4.5"
654
668
  },
655
669
  "deepseek-pro": {
656
670
  displayName: "DeepSeek Pro",
@@ -658,11 +672,13 @@ var providers = {
658
672
  effort: ["high", "xhigh"],
659
673
  openRouterResolve: "openrouter/deepseek/deepseek-v4-pro"
660
674
  },
675
+ // rolling pointer, not the bare id — see `deepseek/deepseek-flash` for why
676
+ // OpenRouter's unversioned `deepseek-v4-flash` is the stale April preview.
661
677
  "deepseek-flash": {
662
678
  displayName: "DeepSeek Flash",
663
- resolve: "openrouter/deepseek/deepseek-v4-flash-0731",
679
+ resolve: "openrouter/~deepseek/deepseek-v4-flash-latest",
664
680
  effort: ["low", "high", "max"],
665
- openRouterResolve: "openrouter/deepseek/deepseek-v4-flash-0731"
681
+ openRouterResolve: "openrouter/~deepseek/deepseek-v4-flash-latest"
666
682
  },
667
683
  // legacy alias — deepseek retires this on 2026-07-24; transparently
668
684
  // upgrade existing users to the v4 family via the fallback chain.
@@ -817,10 +833,12 @@ var OSS_MODEL_ALLOWLIST = [
817
833
  "deepseek/deepseek-flash",
818
834
  "deepseek/deepseek-pro",
819
835
  "openai/gpt-luna",
820
- "openrouter/minimax-m2.5",
821
- "moonshotai/kimi-k2"
836
+ "openrouter/minimax-m2.5"
822
837
  ];
823
838
  var OSS_RECOMMENDED_MODEL = AUTO_TIER_TARGET[AUTO_EFFICIENT];
839
+ function ossFundedModelNames() {
840
+ return OSS_MODEL_ALLOWLIST.map((slug) => resolveDisplayAlias(slug)?.displayName ?? slug);
841
+ }
824
842
  var ossAllowedTargets = new Set(
825
843
  OSS_MODEL_ALLOWLIST.map((slug) => {
826
844
  const target = resolveOpenRouterModel(slug);
@@ -1290,7 +1308,7 @@ function formatModelLabel(params) {
1290
1308
  }
1291
1309
  const base = alias?.isFree ? `\`${displayName}\` (free)` : `\`${displayName}\``;
1292
1310
  if (params.fallbackFrom) {
1293
- return `${base} (credentials for ${providerDisplayName(params.fallbackFrom)} not configured)`;
1311
+ return `${base} (credentials for ${providerDisplayName(params.fallbackFrom)} were rejected by the provider)`;
1294
1312
  }
1295
1313
  if (params.clamped) {
1296
1314
  const target = isAutoTier(params.clamped.from) ? "the intelligent tier" : `\`${resolveDisplayAlias(params.clamped.from)?.displayName ?? params.clamped.from}\``;
@@ -1418,6 +1436,7 @@ function parseCodexAuthBody(raw) {
1418
1436
  if (typeof t.refresh_token !== "string" || t.refresh_token.length === 0) return null;
1419
1437
  return {
1420
1438
  auth_mode: "chatgpt",
1439
+ ...typeof v.refresh_rejected_at === "string" ? { refresh_rejected_at: v.refresh_rejected_at } : {},
1421
1440
  tokens: {
1422
1441
  access_token: t.access_token,
1423
1442
  refresh_token: t.refresh_token,
@@ -1432,6 +1451,109 @@ function stringifyCodexAuthBody(body) {
1432
1451
  `;
1433
1452
  }
1434
1453
 
1454
+ // utils/claudeSubscription.ts
1455
+ var CLAUDE_CODE_IDENTITY = "You are Claude Code, Anthropic's official CLI for Claude.";
1456
+ var fallbackResolve = resolveModelSlug("anthropic/claude-haiku");
1457
+ if (!fallbackResolve) {
1458
+ throw new Error("claudeSubscription preflight: anthropic/claude-haiku missing from registry");
1459
+ }
1460
+ var FALLBACK_PROBE_MODEL = fallbackResolve.slice(fallbackResolve.indexOf("/") + 1);
1461
+ async function preflightClaudeSubscription(params) {
1462
+ let res;
1463
+ try {
1464
+ res = await fetch("https://api.anthropic.com/v1/messages", {
1465
+ method: "POST",
1466
+ headers: {
1467
+ authorization: `Bearer ${params.token}`,
1468
+ "anthropic-beta": "claude-code-20250219,oauth-2025-04-20",
1469
+ "anthropic-version": "2023-06-01",
1470
+ "content-type": "application/json",
1471
+ "x-app": "cli"
1472
+ },
1473
+ body: JSON.stringify({
1474
+ model: params.model ?? FALLBACK_PROBE_MODEL,
1475
+ max_tokens: 1,
1476
+ system: CLAUDE_CODE_IDENTITY,
1477
+ messages: [{ role: "user", content: "ok" }]
1478
+ }),
1479
+ signal: AbortSignal.timeout(1e4)
1480
+ });
1481
+ } catch {
1482
+ return { usable: true, status: void 0 };
1483
+ }
1484
+ if (res.status !== 401 && res.status !== 403 && res.status !== 429)
1485
+ return { usable: true, status: res.status };
1486
+ const body = await res.text().catch(() => "");
1487
+ return {
1488
+ usable: false,
1489
+ status: res.status,
1490
+ reason: `${res.status}: ${extractApiErrorMessage(body)}`
1491
+ };
1492
+ }
1493
+ function extractApiErrorMessage(body) {
1494
+ try {
1495
+ const parsed = JSON.parse(body);
1496
+ if (typeof parsed === "object" && parsed !== null && "error" in parsed && typeof parsed.error === "object" && parsed.error !== null && "message" in parsed.error && typeof parsed.error.message === "string") {
1497
+ return parsed.error.message;
1498
+ }
1499
+ } catch {
1500
+ }
1501
+ return body.slice(0, 200);
1502
+ }
1503
+
1504
+ // utils/credentialCheck.ts
1505
+ var PROBES = {
1506
+ ANTHROPIC_API_KEY: {
1507
+ request: (value) => ({
1508
+ url: "https://api.anthropic.com/v1/models",
1509
+ headers: { "x-api-key": value, "anthropic-version": "2023-06-01" }
1510
+ })
1511
+ },
1512
+ OPENROUTER_API_KEY: {
1513
+ request: (value) => ({
1514
+ url: "https://openrouter.ai/api/v1/key",
1515
+ headers: { authorization: `Bearer ${value}` }
1516
+ })
1517
+ },
1518
+ GEMINI_API_KEY: {
1519
+ request: (value) => ({
1520
+ url: `https://generativelanguage.googleapis.com/v1beta/models?key=${encodeURIComponent(value)}`,
1521
+ headers: {}
1522
+ }),
1523
+ deadWhen: (status, body) => status === 400 && body.includes("API_KEY_INVALID")
1524
+ }
1525
+ };
1526
+ PROBES.GOOGLE_GENERATIVE_AI_API_KEY = PROBES.GEMINI_API_KEY;
1527
+ async function verifyClaudeSubscription(value) {
1528
+ const preflight = await preflightClaudeSubscription({ token: value, model: void 0 });
1529
+ if (preflight.status === void 0) return "unknown";
1530
+ return preflight.status === 401 ? "dead" : "alive";
1531
+ }
1532
+ function isCredentialProbeable(envVar) {
1533
+ return envVar === "CLAUDE_CODE_OAUTH_TOKEN" || envVar in PROBES;
1534
+ }
1535
+ async function verifyCredential(params) {
1536
+ const value = params.value.trim();
1537
+ if (value.length === 0) return "dead";
1538
+ if (params.envVar === "CLAUDE_CODE_OAUTH_TOKEN") return verifyClaudeSubscription(value);
1539
+ const probe = PROBES[params.envVar];
1540
+ if (!probe) return "unknown";
1541
+ const request = probe.request(value);
1542
+ try {
1543
+ const response = await fetch(request.url, {
1544
+ headers: request.headers,
1545
+ signal: AbortSignal.timeout(5e3)
1546
+ });
1547
+ if (response.ok) return "alive";
1548
+ if (probe.deadWhen) {
1549
+ return probe.deadWhen(response.status, await response.text()) ? "dead" : "unknown";
1550
+ }
1551
+ return response.status === 401 ? "dead" : "unknown";
1552
+ } catch {
1553
+ return "unknown";
1554
+ }
1555
+ }
1556
+
1435
1557
  // utils/isPullfrog.ts
1436
1558
  function isPullfrog(actor) {
1437
1559
  actor = actor?.toLowerCase().replace("[bot]", "");
@@ -1557,6 +1679,7 @@ async function createLeapingProgressComment(ctx, target, body) {
1557
1679
 
1558
1680
  // utils/runStatusCheck.ts
1559
1681
  var RUN_STATUS_CHECK_NAME = "pullfrog";
1682
+ var GITHUB_ACTIONS_APP_SLUG = "github-actions";
1560
1683
  var APPROVAL_CHECK_NAME = "pullfrog-approval";
1561
1684
  var IN_PROGRESS_OUTPUT = {
1562
1685
  title: "Pullfrog is running",
@@ -1596,10 +1719,6 @@ var TERMINAL_OUTPUT = {
1596
1719
  skipped: {
1597
1720
  title: "Pullfrog run skipped",
1598
1721
  summary: "This run was superseded by another Pullfrog run."
1599
- },
1600
- stale: {
1601
- title: "Pullfrog run didn't finish",
1602
- summary: "Pullfrog never received a completion signal for this run. See the run logs for details."
1603
1722
  }
1604
1723
  };
1605
1724
  function terminalOutput(params) {
@@ -1618,7 +1737,9 @@ async function createRunStatusCheck(params) {
1618
1737
  check_name: RUN_STATUS_CHECK_NAME,
1619
1738
  status: "in_progress"
1620
1739
  }).catch(() => void 0);
1621
- const reusable = existing?.data.check_runs[0];
1740
+ const reusable = existing?.data.check_runs.find(
1741
+ (run) => run.app?.slug !== GITHUB_ACTIONS_APP_SLUG
1742
+ );
1622
1743
  if (reusable) return reusable.id;
1623
1744
  const createParams = {
1624
1745
  owner: params.owner,
@@ -1708,6 +1829,7 @@ export {
1708
1829
  getProviderDisplayName,
1709
1830
  isAutoTier,
1710
1831
  isCardGatedModel,
1832
+ isCredentialProbeable,
1711
1833
  isEffortPosition,
1712
1834
  isLeapingIntoActionCommentBody,
1713
1835
  isOssAllowedModel,
@@ -1717,6 +1839,7 @@ export {
1717
1839
  modelHasStoredAuth,
1718
1840
  modes,
1719
1841
  offeredRungs,
1842
+ ossFundedModelNames,
1720
1843
  parseCodexAuthBody,
1721
1844
  parseEffortPosition,
1722
1845
  parseModel,
@@ -1737,5 +1860,6 @@ export {
1737
1860
  stringifyCodexAuthBody,
1738
1861
  stripExistingFooter,
1739
1862
  truncateAtLineBoundary,
1740
- updateProgressComment
1863
+ updateProgressComment,
1864
+ verifyCredential
1741
1865
  };
package/dist/mcp/git.d.ts CHANGED
@@ -32,6 +32,33 @@ export declare function CommitChangesTool(ctx: ToolContext): import("fastmcp").T
32
32
  export declare const AUTH_REQUIRED_REDIRECT: Record<string, string>;
33
33
  export declare const NOSHELL_BLOCKED_SUBCOMMANDS: Record<string, string>;
34
34
  export declare const NOSHELL_BLOCKED_ARGS: string[];
35
+ /**
36
+ * Two malformed-but-unambiguous shapes the models keep emitting: `args[0]`
37
+ * repeating the subcommand, and the whole cmdline pushed into `args` behind
38
+ * `command: "git"`. Both were rejected (or, for the second, run as `git git
39
+ * log …`) and the models re-emitted the byte-identical call rather than
40
+ * adapting — 368 dead round trips over 190 runs in 24h (#1199). The intent is
41
+ * knowable, so normalize rather than reject.
42
+ *
43
+ * The promotion out of `command: "git"` re-clears `subcommandPattern`: without
44
+ * that, `args: ["-c", "alias.x=!evil", "x"]` would move a global git option
45
+ * into the subcommand slot that pattern exists to guard.
46
+ */
47
+ export declare function normalizeGitInvocation(input: {
48
+ command: string;
49
+ args: string[];
50
+ }): {
51
+ command: string;
52
+ args: string[];
53
+ };
54
+ /**
55
+ * git subcommands whose exit 1 is a documented result rather than a failure.
56
+ * The status code IS the answer and both streams are empty by design, so
57
+ * anything that reads "non-zero means broken" reports a successful negative as
58
+ * an error. See #1152; `merge-base --is-ancestor` keeps its own branch because
59
+ * it returns a boolean rather than a note.
60
+ */
61
+ export declare function describeEmptyResult(command: string, args: string[]): string | null;
35
62
  export declare function GitTool(ctx: ToolContext): import("fastmcp").Tool<any, import("@standard-schema/spec").StandardSchemaV1<{
36
63
  command: string;
37
64
  args?: string[];
@@ -189,8 +189,8 @@ export declare const CreatePullRequestReview: import("arktype/internal/variants/
189
189
  line: number;
190
190
  side?: "LEFT" | "RIGHT";
191
191
  body?: string;
192
- suggestion?: string;
193
- start_line?: number;
192
+ suggestion?: string | null;
193
+ start_line?: number | null;
194
194
  }[];
195
195
  }, {}>;
196
196
  export declare function CreatePullRequestReviewTool(ctx: ToolContext): import("fastmcp").Tool<any, import("@standard-schema/spec").StandardSchemaV1<{
@@ -204,8 +204,8 @@ export declare function CreatePullRequestReviewTool(ctx: ToolContext): import("f
204
204
  line: number;
205
205
  side?: "LEFT" | "RIGHT";
206
206
  body?: string;
207
- suggestion?: string;
208
- start_line?: number;
207
+ suggestion?: string | null;
208
+ start_line?: number | null;
209
209
  }[];
210
210
  }, {
211
211
  pull_number: number;
@@ -218,8 +218,8 @@ export declare function CreatePullRequestReviewTool(ctx: ToolContext): import("f
218
218
  line: number;
219
219
  side?: "LEFT" | "RIGHT";
220
220
  body?: string;
221
- suggestion?: string;
222
- start_line?: number;
221
+ suggestion?: string | null;
222
+ start_line?: number | null;
223
223
  }[];
224
224
  }>> & {
225
225
  mutates?: boolean;
@@ -1,6 +1,6 @@
1
1
  import type { Octokit } from "@octokit/rest";
2
2
  import type { ToolContext } from "./server.ts";
3
- export declare const REVIEW_THREADS_QUERY = "\nquery ($owner: String!, $name: String!, $prNumber: Int!) {\n repository(owner: $owner, name: $name) {\n pullRequest(number: $prNumber) {\n reviewThreads(first: 100) {\n nodes {\n id\n path\n line\n startLine\n diffSide\n isResolved\n isOutdated\n comments(first: 50) {\n nodes {\n fullDatabaseId\n body\n bodyHTML\n createdAt\n diffHunk\n line\n startLine\n originalLine\n originalStartLine\n author { login }\n pullRequestReview {\n databaseId\n author { login }\n }\n reactionGroups {\n content\n reactors(first: 10) {\n nodes {\n ... on Actor { login }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n}\n";
3
+ export declare const REVIEW_THREADS_QUERY = "\nquery ($owner: String!, $name: String!, $prNumber: Int!, $cursor: String) {\n repository(owner: $owner, name: $name) {\n pullRequest(number: $prNumber) {\n reviewThreads(first: 100, after: $cursor) {\n pageInfo { hasNextPage endCursor }\n nodes {\n id\n path\n line\n startLine\n diffSide\n isResolved\n isOutdated\n comments(first: 50) {\n nodes {\n fullDatabaseId\n body\n bodyHTML\n createdAt\n diffHunk\n line\n startLine\n originalLine\n originalStartLine\n author { login }\n pullRequestReview {\n databaseId\n author { login }\n }\n reactionGroups {\n content\n reactors(first: 10) {\n nodes {\n ... on Actor { login }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n}\n";
4
4
  export type ReviewThreadComment = {
5
5
  fullDatabaseId: string | null;
6
6
  body: string;
@@ -45,6 +45,10 @@ export type ReviewThreadsQueryResponse = {
45
45
  repository: {
46
46
  pullRequest: {
47
47
  reviewThreads: {
48
+ pageInfo: {
49
+ hasNextPage: boolean;
50
+ endCursor: string | null;
51
+ };
48
52
  nodes: (ReviewThread | null)[] | null;
49
53
  } | null;
50
54
  } | null;
@@ -6,7 +6,7 @@ export declare const ShellParams: import("arktype/internal/variants/object.ts").
6
6
  working_directory?: string;
7
7
  background?: boolean;
8
8
  }, {}>;
9
- export type SandboxMethod = "unshare" | "sudo-unshare" | "none";
9
+ export type SandboxMethod = "unshare" | "sudo-unshare" | "userns-unshare" | "none";
10
10
  /** get the current sandbox method (for testing/diagnostics) */
11
11
  export declare function getSandboxMethod(): SandboxMethod;
12
12
  /** chars of shell output kept inline in the agent reply. anything past this
package/dist/models.d.ts CHANGED
@@ -38,7 +38,10 @@ export interface ModelAlias {
38
38
  openRouterResolve: string | undefined;
39
39
  /** top-tier pick for this provider — preferred during auto-select */
40
40
  preferred: boolean;
41
- /** whether this alias is free and requires no API key */
41
+ /** whether this alias costs nothing to run. NOT the same as needing no
42
+ * credential: Zen's free models still require `OPENCODE_API_KEY`, and reading
43
+ * this as "keyless" is what let a keyless run boot onto one and die with
44
+ * `No provider available` (#1077). used to skip the proxy mint. */
42
45
  isFree: boolean;
43
46
  /** slug of a replacement model to resolve through. presence means this alias
44
47
  * never runs as-is — resolution redirects to the replacement and it's hidden
@@ -122,6 +125,8 @@ export declare function parseModel(slug: string): {
122
125
  };
123
126
  export declare function getModelProvider(slug: string): string;
124
127
  export declare function getProviderDisplayName(slug: string): string | undefined;
128
+ /** provider-prefixed specifier → bare model id (`anthropic/claude-opus-5` → `claude-opus-5`). */
129
+ export declare function stripProviderPrefix(specifier: string): string;
125
130
  export declare function getModelEnvVars(slug: string): string[];
126
131
  /** managed credentials are authored only via `pullfrog auth <provider>` — they
127
132
  * count as "configured" for hasAnyKey-style UI checks but are never offered as
@@ -245,14 +250,27 @@ export declare const DEFAULT_PROXY_MODEL: string;
245
250
  * DB toggle — changing it is a deploy, which is the right friction. costs below
246
251
  * are modelled on the measured OSS token mix, where cache reads are ~87.5% of
247
252
  * input and list price therefore ranks models WRONG: Luna 0.39x, Flash 0.45x,
248
- * Pro 1.00x, MiniMax M2 1.30x, Kimi 3.88x modelled — but 7.25x as actually
249
- * measured over the pre-#1063 Kimi era, which is the number to trust.
253
+ * Pro 1.00x, MiniMax M2 1.30x.
254
+ *
255
+ * Kimi K2 was funded and then dropped: 13.2x the funded default as MEASURED
256
+ * over 2,203 real runs ($0.4782 vs $0.0362), almost entirely on its cache-read
257
+ * RATE — 8.5x Flash's. it also fails 2.9% of runs against Flash's 0.0%. best
258
+ * achievable was 10.9x (cheapest OpenRouter endpoint), so no routing fixes it.
259
+ * see wiki/oss-model-allowlist.md.
250
260
  */
251
261
  export declare const OSS_MODEL_ALLOWLIST: readonly string[];
252
262
  /** the pick the console badges. DERIVED from the efficient tier rather than
253
263
  * restated, so the badge cannot drift from the model that actually runs — a
254
264
  * second literal here would be an invariant nothing enforces. */
255
265
  export declare const OSS_RECOMMENDED_MODEL: string;
266
+ /**
267
+ * display names of the funded set, in allowlist order. the single place every
268
+ * surface that NAMES the menu in prose reads it from — the application page,
269
+ * the acceptance email, the model-access error — so none of them can promise a
270
+ * model the program stopped funding. callers format the list themselves; this
271
+ * is a catalog, not copy.
272
+ */
273
+ export declare function ossFundedModelNames(): string[];
256
274
  /**
257
275
  * whether an OSS repo's stored pick is one the program funds. resolves through
258
276
  * the fallback chain first, so `auto/*` sentinels and deprecated slugs are
@@ -148,6 +148,13 @@ export interface ToolState {
148
148
  oss?: boolean | undefined;
149
149
  todoTracker?: TodoTracker | undefined;
150
150
  agentDiagnostic?: AgentDiagnostic | undefined;
151
+ /**
152
+ * a `PATCH /api/workflow-run/:id` came back 404 — the reservation was never
153
+ * claimed, which is permanent for the life of the run (the claim happens at
154
+ * setup, strictly before any PATCH). latched so the remaining calls stop
155
+ * firing: one CI run spent 28 doomed round trips on it. see #1153.
156
+ */
157
+ workflowRunUnclaimed?: boolean | undefined;
151
158
  }
152
159
  interface InitToolStateParams {
153
160
  progressComment: {
@@ -5,6 +5,28 @@
5
5
  * on our side is the wrong CTA — their key is configured and still stored.
6
6
  */
7
7
  export declare const SECRETS_UNAVAILABLE_MARKER = "couldn't load your Pullfrog secrets";
8
+ /**
9
+ * marker for the "your Router wallet is empty AND you have no provider key"
10
+ * body. its own marker rather than the missing-key one because
11
+ * `formatApiKeyErrorSummary` rebuilds any body carrying `MISSING_KEY_MARKER`
12
+ * that doesn't start with it, which would silently discard this copy.
13
+ */
14
+ export declare const ROUTER_UNFUNDED_MARKER = "your Pullfrog Router balance is empty";
15
+ /**
16
+ * marker for a credential the provider explicitly REJECTED before the agent
17
+ * started. needs a verbatim guard for the same reason as its three siblings,
18
+ * and more sharply: the body quotes the provider's own wording, which is
19
+ * exactly what `isApiKeyAuthError` sniffs — so an Anthropic error whose JSON
20
+ * carries `authentication_error` would be rebuilt into the generic
21
+ * "rotate the key, update the GitHub Actions secret" copy. That is #782
22
+ * verbatim, on the code path built to stop it.
23
+ *
24
+ * Phrased distinctively for the same reason as its siblings: `runErrorRenderer`
25
+ * short-circuits the whole rendering pipeline on a SUBSTRING match against
26
+ * arbitrary agent output, so a marker short enough to occur by accident would
27
+ * hand that agent the ability to suppress every other error body.
28
+ */
29
+ export declare const CREDENTIAL_REJECTED_MARKER = "was rejected by its provider";
8
30
  /**
9
31
  * Validate that the resolved model can actually be served by the chosen
10
32
  * agent. For routing slugs (Bedrock / Vertex) the auth shape is multi-var
@@ -26,6 +48,7 @@ export declare function validateAgentApiKey(params: {
26
48
  /** run-context couldn't hand over Pullfrog-stored secrets, so a missing key
27
49
  * says nothing about what the user actually configured. */
28
50
  secretsUnavailable?: boolean | undefined;
51
+ routerUnfunded?: boolean | undefined;
29
52
  }): void;
30
53
  /**
31
54
  * Detect agent-runtime auth failures that should be reformatted as an actionable
@@ -68,6 +91,14 @@ export declare function isApiKeyAuthError(text: string): boolean;
68
91
  * expiry can't be misread as an LLM credential problem.
69
92
  */
70
93
  export declare function isOAuthCredentialExpiredError(text: string): boolean;
94
+ /**
95
+ * The key itself is unsendable, not wrong: a line-wrapped paste leaves an
96
+ * interior newline, and the provider SDK refuses to serialize the header
97
+ * (`API Error: Header '14' has invalid value`). The only member of the
98
+ * credential-error family whose remedy is NOT "rotate the key" — re-saving the
99
+ * same value on one line fixes it. See #1162.
100
+ */
101
+ export declare function isMalformedKeyError(text: string): boolean;
71
102
  /**
72
103
  * Anthropic entitlement denial for Claude Pro/Max subscription auth (#1072):
73
104
  * an org admin turned off Claude Code subscription access. no credential the
@@ -75,6 +106,20 @@ export declare function isOAuthCredentialExpiredError(text: string): boolean;
75
106
  */
76
107
  export declare function isClaudeSubscriptionDisabledError(text: string): boolean;
77
108
  export declare function isClaudeSessionLimitError(text: string): boolean;
109
+ /**
110
+ * The provider rejected a credential we checked before the agent started, and
111
+ * nothing else on the account can serve this run. Names the credential and the
112
+ * place it actually lives, rather than guessing from the provider's wording.
113
+ */
114
+ export declare function buildRejectedCredentialError(params: {
115
+ credential: string;
116
+ /** the provider's own message, when it gave one worth quoting. */
117
+ reason: string | undefined;
118
+ owner: string;
119
+ name: string;
120
+ /** stored in Pullfrog (console / `pullfrog auth`) rather than a GitHub Actions secret. */
121
+ inPullfrogStore: boolean;
122
+ }): string;
78
123
  /**
79
124
  * Friendly Markdown summary for both the missing-key and invalid-key cases.
80
125
  * Used in the catch / result-failure paths in `main.ts` to overwrite the raw
@@ -18,10 +18,10 @@ export interface BuildPullfrogFooterParams {
18
18
  /** model slug from payload (e.g., "anthropic/claude-opus"). shown in footer as "Using `Model Name`" */
19
19
  model?: string | undefined;
20
20
  /**
21
- * When the action engaged the BYOK fallback, this is the slug the user
22
- * had configured (e.g. "anthropic/claude-opus") — the footer renders
23
- * `Using <free model> (credentials for <configured> not configured)`
24
- * so the substitution is visible in PR comments + reviews.
21
+ * When a credential was rejected and the run moved to another model, this is
22
+ * the slug the user had configured (e.g. "anthropic/claude-opus") — the footer
23
+ * renders `Using <model> (credentials for <configured> were rejected by the
24
+ * provider)` so the substitution is visible in PR comments + reviews.
25
25
  */
26
26
  fallbackFrom?: string | undefined;
27
27
  /**