pullfrog 0.1.66 → 0.1.68

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 CHANGED
@@ -525,15 +525,15 @@ var require_file_command = __commonJS({
525
525
  }
526
526
  exports.issueFileCommand = issueFileCommand;
527
527
  function prepareKeyValueMessage(key, value2) {
528
- const delimiter2 = `ghadelimiter_${crypto5.randomUUID()}`;
528
+ const delimiter3 = `ghadelimiter_${crypto5.randomUUID()}`;
529
529
  const convertedValue = (0, utils_1.toCommandValue)(value2);
530
- if (key.includes(delimiter2)) {
531
- throw new Error(`Unexpected input: name should not contain the delimiter "${delimiter2}"`);
530
+ if (key.includes(delimiter3)) {
531
+ throw new Error(`Unexpected input: name should not contain the delimiter "${delimiter3}"`);
532
532
  }
533
- if (convertedValue.includes(delimiter2)) {
534
- throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter2}"`);
533
+ if (convertedValue.includes(delimiter3)) {
534
+ throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter3}"`);
535
535
  }
536
- return `${key}<<${delimiter2}${os2.EOL}${convertedValue}${os2.EOL}${delimiter2}`;
536
+ return `${key}<<${delimiter3}${os2.EOL}${convertedValue}${os2.EOL}${delimiter3}`;
537
537
  }
538
538
  exports.prepareKeyValueMessage = prepareKeyValueMessage;
539
539
  }
@@ -11973,10 +11973,10 @@ var require_headers = __commonJS({
11973
11973
  const lowercaseName = name.toLowerCase();
11974
11974
  const exists = this[kHeadersMap].get(lowercaseName);
11975
11975
  if (exists) {
11976
- const delimiter2 = lowercaseName === "cookie" ? "; " : ", ";
11976
+ const delimiter3 = lowercaseName === "cookie" ? "; " : ", ";
11977
11977
  this[kHeadersMap].set(lowercaseName, {
11978
11978
  name: exists.name,
11979
- value: `${exists.value}${delimiter2}${value2}`
11979
+ value: `${exists.value}${delimiter3}${value2}`
11980
11980
  });
11981
11981
  } else {
11982
11982
  this[kHeadersMap].set(lowercaseName, { name, value: value2 });
@@ -46981,10 +46981,10 @@ var require_headers2 = __commonJS({
46981
46981
  const lowercaseName = isLowerCase ? name : name.toLowerCase();
46982
46982
  const exists = this.headersMap.get(lowercaseName);
46983
46983
  if (exists) {
46984
- const delimiter2 = lowercaseName === "cookie" ? "; " : ", ";
46984
+ const delimiter3 = lowercaseName === "cookie" ? "; " : ", ";
46985
46985
  this.headersMap.set(lowercaseName, {
46986
46986
  name: exists.name,
46987
- value: `${exists.value}${delimiter2}${value2}`
46987
+ value: `${exists.value}${delimiter3}${value2}`
46988
46988
  });
46989
46989
  } else {
46990
46990
  this.headersMap.set(lowercaseName, { name, value: value2 });
@@ -59293,8 +59293,8 @@ var init_regexes = __esm({
59293
59293
  _emoji = `^[\\p{Extended_Pictographic}\\p{Emoji_Component}]+$`;
59294
59294
  ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/;
59295
59295
  ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;
59296
- mac = (delimiter2) => {
59297
- const escapedDelim = escapeRegex(delimiter2 ?? ":");
59296
+ mac = (delimiter3) => {
59297
+ const escapedDelim = escapeRegex(delimiter3 ?? ":");
59298
59298
  return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
59299
59299
  };
59300
59300
  cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/;
@@ -102436,10 +102436,10 @@ var require_turndown_cjs = __commonJS({
102436
102436
  if (!content) return "";
102437
102437
  content = content.replace(/\r?\n|\r/g, " ");
102438
102438
  var extraSpace = /^`|^ .*?[^ ].* $|`$/.test(content) ? " " : "";
102439
- var delimiter2 = "`";
102439
+ var delimiter3 = "`";
102440
102440
  var matches = content.match(/`+/gm) || [];
102441
- while (matches.indexOf(delimiter2) !== -1) delimiter2 = delimiter2 + "`";
102442
- return delimiter2 + extraSpace + content + extraSpace + delimiter2;
102441
+ while (matches.indexOf(delimiter3) !== -1) delimiter3 = delimiter3 + "`";
102442
+ return delimiter3 + extraSpace + content + extraSpace + delimiter3;
102443
102443
  }
102444
102444
  };
102445
102445
  rules.image = {
@@ -104189,8 +104189,8 @@ var require_common = __commonJS({
104189
104189
  }
104190
104190
  return debug4;
104191
104191
  }
104192
- function extend3(namespace, delimiter2) {
104193
- const newDebug = createDebug(this.namespace + (typeof delimiter2 === "undefined" ? ":" : delimiter2) + namespace);
104192
+ function extend3(namespace, delimiter3) {
104193
+ const newDebug = createDebug(this.namespace + (typeof delimiter3 === "undefined" ? ":" : delimiter3) + namespace);
104194
104194
  newDebug.log = this.log;
104195
104195
  return newDebug;
104196
104196
  }
@@ -108809,9 +108809,9 @@ var import_picocolors2 = __toESM(require_picocolors(), 1);
108809
108809
 
108810
108810
  // utils/codexAuth.ts
108811
108811
  import { spawn } from "node:child_process";
108812
- import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
108812
+ import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
108813
108813
  import { tmpdir } from "node:os";
108814
- import { join } from "node:path";
108814
+ import { delimiter, join } from "node:path";
108815
108815
 
108816
108816
  // utils/oauthShared.ts
108817
108817
  var OAuthInvalidGrantError = class extends Error {
@@ -108960,11 +108960,60 @@ async function mintCodexAuth(options) {
108960
108960
  }
108961
108961
  }
108962
108962
  var SIGTERM_GRACE_MS = 5e3;
108963
+ var DEVICE_AUTH_ARGS = ["login", "--device-auth"];
108964
+ var DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD";
108965
+ function envValue(env2, name) {
108966
+ if (env2[name] !== void 0) return env2[name];
108967
+ const key = Object.keys(env2).find((candidate) => candidate.toUpperCase() === name.toUpperCase());
108968
+ return key === void 0 ? void 0 : env2[key];
108969
+ }
108970
+ function commandProcessor(env2) {
108971
+ return envValue(env2, "ComSpec") ?? join(envValue(env2, "SystemRoot") ?? "C:\\Windows", "system32", "cmd.exe");
108972
+ }
108973
+ function resolveSpawnTarget(params) {
108974
+ if (process.platform !== "win32") {
108975
+ return { file: params.command, args: params.args, wrapped: false };
108976
+ }
108977
+ const extensions2 = (envValue(params.env, "PATHEXT") ?? DEFAULT_PATHEXT).split(";").filter(Boolean);
108978
+ for (const directory of (envValue(params.env, "PATH") ?? "").split(delimiter).filter(Boolean)) {
108979
+ for (const extension of extensions2) {
108980
+ const candidate = join(directory, `${params.command}${extension.toLowerCase()}`);
108981
+ if (!existsSync(candidate)) continue;
108982
+ if (!/\.(cmd|bat)$/i.test(candidate)) {
108983
+ return { file: candidate, args: params.args, wrapped: false };
108984
+ }
108985
+ const command = [`"${candidate}"`, ...params.args].join(" ");
108986
+ return {
108987
+ file: commandProcessor(params.env),
108988
+ args: ["/d", "/s", "/c", `"${command}"`],
108989
+ wrapped: true
108990
+ };
108991
+ }
108992
+ }
108993
+ return null;
108994
+ }
108995
+ function codexNotFoundMessage(env2) {
108996
+ const install = "install it with `npm i -g @openai/codex` or see https://developers.openai.com/codex/cli for other install options.";
108997
+ if (process.platform !== "win32") return `codex CLI not found on PATH. ${install}`;
108998
+ const extensions2 = (envValue(env2, "PATHEXT") ?? DEFAULT_PATHEXT).split(";").filter(Boolean).join(" ");
108999
+ return `codex CLI not found on PATH (searched every PATH entry for codex with ${extensions2}). a working \`codex --version\` in a shell does not prove Node can spawn it. ${install}`;
109000
+ }
108963
109001
  function runDeviceAuth(input2) {
108964
109002
  return new Promise((resolve3, reject) => {
108965
- const child = spawn("codex", ["login", "--device-auth"], {
109003
+ const target = resolveSpawnTarget({
109004
+ command: "codex",
109005
+ args: DEVICE_AUTH_ARGS,
109006
+ env: process.env
109007
+ });
109008
+ if (!target) {
109009
+ reject(new Error(codexNotFoundMessage(process.env)));
109010
+ return;
109011
+ }
109012
+ const child = spawn(target.file, target.args, {
108966
109013
  env: { ...process.env, CODEX_HOME: input2.codexHome },
108967
- stdio: ["ignore", input2.childStdio, input2.childStdio]
109014
+ stdio: ["ignore", input2.childStdio, input2.childStdio],
109015
+ windowsVerbatimArguments: target.wrapped,
109016
+ windowsHide: true
108968
109017
  });
108969
109018
  if (input2.childStdio === "pipe") {
108970
109019
  const onLine = input2.onChildLine ?? (() => {
@@ -108976,6 +109025,10 @@ function runDeviceAuth(input2) {
108976
109025
  let timedOut = false;
108977
109026
  const timeoutTimer = setTimeout(() => {
108978
109027
  timedOut = true;
109028
+ if (target.wrapped && child.pid) {
109029
+ spawn("taskkill", ["/pid", String(child.pid), "/t", "/f"], { stdio: "ignore" });
109030
+ return;
109031
+ }
108979
109032
  child.kill("SIGTERM");
108980
109033
  killTimer = setTimeout(() => child.kill("SIGKILL"), SIGTERM_GRACE_MS);
108981
109034
  }, input2.timeoutMs);
@@ -108983,7 +109036,7 @@ function runDeviceAuth(input2) {
108983
109036
  clearTimeout(timeoutTimer);
108984
109037
  if (killTimer) clearTimeout(killTimer);
108985
109038
  const errno = err;
108986
- const message = errno.code === "ENOENT" ? "codex CLI not found on PATH. install it with `npm i -g @openai/codex` or see https://developers.openai.com/codex/cli for other install options." : `failed to spawn codex: ${errno.message}`;
109039
+ const message = errno.code === "ENOENT" ? codexNotFoundMessage(process.env) : `failed to spawn codex: ${errno.message}`;
108987
109040
  reject(new Error(message));
108988
109041
  });
108989
109042
  child.on("close", (code, signal) => {
@@ -109797,7 +109850,7 @@ var import_arg2 = __toESM(require_arg(), 1);
109797
109850
  import { dirname as dirname6 } from "node:path";
109798
109851
 
109799
109852
  // main.ts
109800
- import { existsSync as existsSync10, readdirSync as readdirSync2 } from "node:fs";
109853
+ import { existsSync as existsSync11, readdirSync as readdirSync2 } from "node:fs";
109801
109854
  import { readFile as readFile5 } from "node:fs/promises";
109802
109855
  import { join as join28 } from "node:path";
109803
109856
 
@@ -110155,6 +110208,7 @@ import { performance as performance5 } from "node:perf_hooks";
110155
110208
 
110156
110209
  // effort.ts
110157
110210
  var DEFAULT_EFFORT_POSITION = 0.5;
110211
+ var SUBSIDY_RUNG = "high";
110158
110212
  var EFFORT_ALIASES = {
110159
110213
  low: 0,
110160
110214
  medium: 0.25,
@@ -110479,6 +110533,69 @@ var providers = {
110479
110533
  }
110480
110534
  }
110481
110535
  }),
110536
+ // the same models as `moonshotai` above, billed against a Kimi membership
110537
+ // instead of a Moonshot API balance — a separate endpoint, separate model
110538
+ // ids and a separate key, which is why it is a provider rather than a second
110539
+ // `envVars` entry. no provider injection is needed: models.dev carries
110540
+ // `kimi-for-coding` (api `https://api.kimi.com/coding/v1`, npm
110541
+ // `@ai-sdk/anthropic`) and opencode's env loop enables it from a bare
110542
+ // `KIMI_API_KEY`, verified against our pinned opencode-ai@1.18.5 — all four
110543
+ // ids list in `opencode models` and a run reaches Kimi's own auth check.
110544
+ //
110545
+ // NO `openRouterResolve` ON PURPOSE. OpenRouter cannot serve a Kimi
110546
+ // membership, so a Router path here would silently bill the wallet for
110547
+ // Moonshot pay-as-you-go under a slug picked for the opposite reason. The
110548
+ // absence is load-bearing: `isCardGatedModel` reads it as "not Router-
110549
+ // resolvable", `decideModelAccess` answers an explicit pick with the
110550
+ // `router` copy ("add your own provider key"), and run-context clamps a
110551
+ // keyless Router pick to the default subsidy model with `noRouterPath`.
110552
+ "kimi-for-coding": provider({
110553
+ // "Kimi Code" is the product name on kimi.com/code and in the console the
110554
+ // key is minted from. The provider KEY has to stay `kimi-for-coding` — it
110555
+ // is models.dev's id, so it is what `enabled_providers` and every
110556
+ // `resolve` prefix must spell.
110557
+ displayName: "Kimi Code",
110558
+ envVars: ["KIMI_API_KEY"],
110559
+ models: {
110560
+ // the tier each model needs is in `description` because Kimi answers an
110561
+ // out-of-tier request with 401 — indistinguishable from a bad key by
110562
+ // status alone, and only `isKimiPlanTierError` tells them apart after the
110563
+ // fact. saying it in the picker is the half that stops the run happening.
110564
+ "kimi-k3": {
110565
+ displayName: "Kimi K3",
110566
+ description: "1M context, but only on Allegretto and above \xB7 Moderato caps it at 256K",
110567
+ resolve: "kimi-for-coding/k3",
110568
+ effort: ["low", "high", "max"],
110569
+ subagentModel: "kimi-k2"
110570
+ },
110571
+ // `preferred` sits on the 256K variant rather than the 1M flagship, which
110572
+ // inverts the usual "top tier wins" rule for a reason specific to Kimi:
110573
+ // below Allegretto the two are the SAME 256K ceiling and `k3` charges
110574
+ // roughly double the quota for it, so on the median tier the flagship is
110575
+ // strictly dominated rather than merely pricier. This flag seeds the
110576
+ // onboarding pick (`getRecommendedSlug`) and wins key-only auto-select
110577
+ // (`autoSelectModel`), which are exactly the two moments nobody has
110578
+ // chosen yet.
110579
+ "kimi-k3-256k": {
110580
+ displayName: "Kimi K3 256K",
110581
+ description: "Same model at 256K for ~half the quota \xB7 Moderato or above",
110582
+ resolve: "kimi-for-coding/k3-256k",
110583
+ effort: ["low", "high", "max"],
110584
+ preferred: true,
110585
+ subagentModel: "kimi-k2"
110586
+ },
110587
+ "kimi-k2": {
110588
+ displayName: "Kimi K2",
110589
+ description: "Included in every Kimi membership tier",
110590
+ resolve: "kimi-for-coding/kimi-for-coding"
110591
+ },
110592
+ "kimi-k2-highspeed": {
110593
+ displayName: "Kimi K2 HighSpeed",
110594
+ description: "~6x output speed for ~3x quota \xB7 needs Allegretto or above",
110595
+ resolve: "kimi-for-coding/kimi-for-coding-highspeed"
110596
+ }
110597
+ }
110598
+ }),
110482
110599
  opencode: provider({
110483
110600
  displayName: "OpenCode Zen",
110484
110601
  envVars: ["OPENCODE_API_KEY"],
@@ -110495,6 +110612,15 @@ var providers = {
110495
110612
  // the model does cost nothing.
110496
110613
  isFree: true
110497
110614
  },
110615
+ // Zen meters Fable like any other model, so this route reaches it without
110616
+ // the Anthropic access grant `anthropic/claude-fable` still needs.
110617
+ "claude-fable": {
110618
+ displayName: "Claude Fable",
110619
+ resolve: "opencode/claude-fable-5-1",
110620
+ effort: ["low", "medium", "high", "xhigh", "max"],
110621
+ openRouterResolve: "openrouter/~anthropic/claude-fable-latest",
110622
+ subagentModel: "claude-sonnet"
110623
+ },
110498
110624
  "claude-opus": {
110499
110625
  displayName: "Claude Opus",
110500
110626
  resolve: "opencode/claude-opus-5",
@@ -110608,6 +110734,17 @@ var providers = {
110608
110734
  effort: ["minimal", "low", "medium", "high"],
110609
110735
  openRouterResolve: "openrouter/google/gemini-3.6-flash"
110610
110736
  },
110737
+ // Zen serves K3, but rule 7 can only move a mirror when its upstream moves,
110738
+ // so the K3 generation could never reach this block by bumping `kimi-k2`.
110739
+ "kimi-k3": {
110740
+ displayName: "Kimi K3",
110741
+ resolve: "opencode/kimi-k3",
110742
+ // Zen publishes a single rung for K3 where OpenRouter publishes three.
110743
+ effort: ["max"],
110744
+ openRouterEffort: ["low", "high", "max"],
110745
+ openRouterResolve: "openrouter/moonshotai/kimi-k3",
110746
+ subagentModel: "kimi-k2"
110747
+ },
110611
110748
  "kimi-k2": {
110612
110749
  displayName: "Kimi K2",
110613
110750
  // k2.7-code is UNDEPLOYED on Zen — still listed in /zen/v1/models, but
@@ -110619,25 +110756,58 @@ var providers = {
110619
110756
  resolve: "opencode/kimi-k2.6",
110620
110757
  openRouterResolve: "openrouter/moonshotai/kimi-k2.7-code"
110621
110758
  },
110759
+ // M3 is its own line beside M2, not a bump of it — same reason `kimi-k3`
110760
+ // sits beside `kimi-k2` rather than replacing it.
110761
+ "minimax-m3": {
110762
+ displayName: "MiniMax M3",
110763
+ resolve: "opencode/minimax-m3",
110764
+ openRouterResolve: "openrouter/minimax/minimax-m3"
110765
+ },
110622
110766
  // slug pins the m2 line for DB stability; resolve tracks the current m2.7.
110623
110767
  "minimax-m2.5": {
110624
110768
  displayName: "MiniMax M2",
110625
110769
  resolve: "opencode/minimax-m2.7",
110626
110770
  openRouterResolve: "openrouter/minimax/minimax-m2.7"
110627
110771
  },
110772
+ // Z.ai and xAI reach Zen subscribers only here — before this, `opencode-go`
110773
+ // was the catalog's only GLM route, and Grok had no Zen route at all.
110774
+ glm: {
110775
+ displayName: "GLM",
110776
+ resolve: "opencode/glm-5.2",
110777
+ effort: ["high", "max"],
110778
+ openRouterEffort: ["high", "xhigh"],
110779
+ openRouterResolve: "openrouter/z-ai/glm-5.2"
110780
+ },
110781
+ grok: {
110782
+ displayName: "Grok",
110783
+ resolve: "opencode/grok-4.6",
110784
+ effort: ["low", "medium", "high", "xhigh"],
110785
+ openRouterResolve: "openrouter/x-ai/grok-4.6"
110786
+ },
110628
110787
  "gpt-5-nano": {
110629
110788
  displayName: "GPT Nano",
110630
110789
  resolve: "opencode/gpt-5-nano",
110631
110790
  effort: ["minimal", "low", "medium", "high"],
110632
110791
  openRouterResolve: "openrouter/openai/gpt-5-nano"
110633
110792
  },
110793
+ // Zen's live free MiMo, and the second free row in a menu that big-pickle
110794
+ // was alone in since `mimo-v2-pro-free` lost its model.
110795
+ mimo: {
110796
+ displayName: "MiMo",
110797
+ resolve: "opencode/mimo-v2.5-free",
110798
+ // free to run, still gated on the provider's own OPENCODE_API_KEY —
110799
+ // see the big-pickle note above (#1077).
110800
+ isFree: true
110801
+ },
110634
110802
  "mimo-v2-pro-free": {
110635
110803
  displayName: "MiMo V2 Pro",
110636
110804
  resolve: "opencode/mimo-v2-pro-free",
110637
110805
  // free to run, still gated on the provider's own OPENCODE_API_KEY —
110638
110806
  // see the big-pickle note above (#1077).
110639
110807
  isFree: true,
110640
- fallback: "opencode/big-pickle"
110808
+ // the model id Zen dropped; land a stored MiMo pick back on MiMo rather
110809
+ // than on the unrelated model it had to settle for while none was live.
110810
+ fallback: "opencode/mimo"
110641
110811
  },
110642
110812
  "minimax-m2.5-free": {
110643
110813
  displayName: "MiniMax M2",
@@ -110732,6 +110902,16 @@ var providers = {
110732
110902
  resolve: "opencode-go/qwen3.7-plus",
110733
110903
  openRouterResolve: "openrouter/qwen/qwen3.7-plus"
110734
110904
  },
110905
+ // the cheap rung Alibaba added under Plus (0.15/0.47 against 0.5/3), and
110906
+ // the only model either OpenCode plan has added since this block was written.
110907
+ "qwen-flash": {
110908
+ displayName: "Qwen Flash",
110909
+ resolve: "opencode-go/qwen3.8-flash",
110910
+ effort: ["low", "medium", "xhigh"],
110911
+ // the Go route publishes rungs where OpenRouter publishes none.
110912
+ openRouterEffort: [],
110913
+ openRouterResolve: "openrouter/qwen/qwen3.8-flash"
110914
+ },
110735
110915
  // MiniMax — parity with opencode/* and openrouter/*; the m2 slug pins the
110736
110916
  // line for DB stability while the resolve tracks the current m2.7.
110737
110917
  "minimax-m3": {
@@ -111031,6 +111211,80 @@ var providers = {
111031
111211
  openRouterResolve: "openrouter/minimax/minimax-m3"
111032
111212
  }
111033
111213
  }
111214
+ }),
111215
+ vercel: provider({
111216
+ // Vercel AI Gateway — one key serves every model it fronts. model IDs use
111217
+ // the models.dev `vercel` catalog's own naming (dotted versions, e.g.
111218
+ // `anthropic/claude-opus-5`, `zai/glm-5.3`), and effort ladders mirror that
111219
+ // catalog's entries, not the upstream vendor's. deliberately no
111220
+ // `openRouterResolve`: a gateway pick is BYOK-only — silently rerouting it
111221
+ // over the Router proxy would bill the wallet for traffic the user pointed
111222
+ // at their own gateway.
111223
+ displayName: "Vercel AI Gateway",
111224
+ envVars: ["AI_GATEWAY_API_KEY"],
111225
+ models: {
111226
+ "claude-opus": {
111227
+ displayName: "Claude Opus",
111228
+ resolve: "vercel/anthropic/claude-opus-5",
111229
+ effort: ["low", "medium", "high", "xhigh", "max"],
111230
+ preferred: true,
111231
+ subagentModel: "claude-sonnet"
111232
+ },
111233
+ "claude-sonnet": {
111234
+ displayName: "Claude Sonnet",
111235
+ resolve: "vercel/anthropic/claude-sonnet-5",
111236
+ effort: ["low", "medium", "high", "xhigh"]
111237
+ },
111238
+ "claude-haiku": {
111239
+ displayName: "Claude Haiku",
111240
+ resolve: "vercel/anthropic/claude-haiku-4.5"
111241
+ },
111242
+ "gpt-sol": {
111243
+ displayName: "GPT Sol",
111244
+ resolve: "vercel/openai/gpt-5.6-sol",
111245
+ effort: ["none", "low", "medium", "high", "xhigh", "max"],
111246
+ subagentModel: "gpt-terra"
111247
+ },
111248
+ "gpt-terra": {
111249
+ displayName: "GPT Terra",
111250
+ resolve: "vercel/openai/gpt-5.6-terra",
111251
+ effort: ["none", "low", "medium", "high", "xhigh", "max"]
111252
+ },
111253
+ "gpt-luna": {
111254
+ displayName: "GPT Luna",
111255
+ resolve: "vercel/openai/gpt-5.6-luna",
111256
+ effort: ["none", "low", "medium", "high", "xhigh", "max"]
111257
+ },
111258
+ "gemini-pro": {
111259
+ displayName: "Gemini Pro",
111260
+ resolve: "vercel/google/gemini-3.1-pro-preview",
111261
+ effort: ["low", "medium", "high"]
111262
+ },
111263
+ "gemini-flash": {
111264
+ displayName: "Gemini Flash",
111265
+ resolve: "vercel/google/gemini-3.6-flash",
111266
+ effort: ["minimal", "low", "medium", "high"]
111267
+ },
111268
+ "deepseek-pro": {
111269
+ displayName: "DeepSeek Pro",
111270
+ resolve: "vercel/deepseek/deepseek-v4-pro-0813",
111271
+ effort: ["high", "xhigh"]
111272
+ },
111273
+ "deepseek-flash": {
111274
+ displayName: "DeepSeek Flash",
111275
+ resolve: "vercel/deepseek/deepseek-v4-flash",
111276
+ effort: ["high", "xhigh"]
111277
+ },
111278
+ glm: {
111279
+ displayName: "GLM",
111280
+ resolve: "vercel/zai/glm-5.3",
111281
+ effort: ["low", "high", "max"]
111282
+ },
111283
+ "kimi-k3": {
111284
+ displayName: "Kimi K3",
111285
+ resolve: "vercel/moonshotai/kimi-k3"
111286
+ }
111287
+ }
111034
111288
  })
111035
111289
  };
111036
111290
  function parseModel(slug2) {
@@ -111458,9 +111712,9 @@ function logTokenTable(t2) {
111458
111712
  }
111459
111713
 
111460
111714
  // utils/globals.ts
111461
- import { existsSync } from "node:fs";
111715
+ import { existsSync as existsSync2 } from "node:fs";
111462
111716
  var isGitHubActions = !!process.env.GITHUB_ACTIONS;
111463
- var isInsideDocker = existsSync("/.dockerenv");
111717
+ var isInsideDocker = existsSync2("/.dockerenv");
111464
111718
 
111465
111719
  // utils/log.ts
111466
111720
  var logContext = new AsyncLocalStorage();
@@ -111688,7 +111942,7 @@ function formatUsageSummary(entries) {
111688
111942
 
111689
111943
  // utils/install.ts
111690
111944
  import { spawnSync } from "node:child_process";
111691
- import { chmodSync, createWriteStream, existsSync as existsSync2, mkdirSync } from "node:fs";
111945
+ import { chmodSync, createWriteStream, existsSync as existsSync3, mkdirSync } from "node:fs";
111692
111946
  import { join as join2 } from "node:path";
111693
111947
  import { pipeline } from "node:stream/promises";
111694
111948
  async function installFromNpmTarball(params) {
@@ -111697,7 +111951,7 @@ async function installFromNpmTarball(params) {
111697
111951
  const packageRoot = join2(tempDir, `npm-${params.packageName.replace(/[^a-z0-9]+/gi, "-")}`);
111698
111952
  const extractedDir = join2(packageRoot, "package");
111699
111953
  const cliPath = join2(extractedDir, params.executablePath);
111700
- if (existsSync2(cliPath)) {
111954
+ if (existsSync3(cliPath)) {
111701
111955
  log2.debug(`\xBB using cached binary at ${cliPath}`);
111702
111956
  return cliPath;
111703
111957
  }
@@ -111771,7 +112025,7 @@ async function installFromNpmTarball(params) {
111771
112025
  }
111772
112026
  log2.debug(`\xBB dependencies installed`);
111773
112027
  }
111774
- if (!existsSync2(cliPath)) {
112028
+ if (!existsSync3(cliPath)) {
111775
112029
  throw new Error(`Executable not found in extracted package at ${cliPath}`);
111776
112030
  }
111777
112031
  chmodSync(cliPath, 493);
@@ -111782,6 +112036,7 @@ async function installFromNpmTarball(params) {
111782
112036
  // utils/providerErrors.ts
111783
112037
  var PROVIDER_BILLING_EXHAUSTED_LABEL = "provider billing exhausted";
111784
112038
  var PROVIDER_NO_ENDPOINTS_LABEL = "provider no routable endpoints";
112039
+ var PROVIDER_USAGE_LIMIT_LABEL = "provider usage limit reached";
111785
112040
  var statusKey = `\\b(?:status[_ ]?code|http[_ ]?status|status)["']?\\s*[:=]\\s*["']?`;
111786
112041
  var ANTHROPIC_SPEND_CAP_PATTERN = /reached your specified API usage limits\.?(?:\s*You will regain access on ([^"\n]+?)\.)?/i;
111787
112042
  function findAnthropicSpendCap(text) {
@@ -111827,6 +112082,14 @@ var PROVIDER_ERROR_PATTERNS = [
111827
112082
  // the wallet-shaped patterns above reach, and its 400 sits nowhere near a
111828
112083
  // `status:` key either — so the whole message was dropped on the floor (#1208).
111829
112084
  { regex: ANTHROPIC_SPEND_CAP_PATTERN, label: PROVIDER_BILLING_EXHAUSTED_LABEL },
112085
+ // OpenCode Zen / Codex-subscription cap: `AI_APICallError: The usage limit has
112086
+ // been reached`. Its noun is "usage limit" with no "rate", no status code and
112087
+ // no wallet wording, so every pattern here missed it and `lastProviderError`
112088
+ // stayed unset — which suppresses nothing, so the run was reported as "The
112089
+ // model produced no output at all … usually transient; re-running often
112090
+ // succeeds" while the same log held nine of these. Measured on a live account
112091
+ // whose ChatGPT subscription was capped.
112092
+ { regex: /\bthe usage limit has been reached\b/i, label: PROVIDER_USAGE_LIMIT_LABEL },
111830
112093
  // the OpenRouter shapes `isRouterKeylimitExhaustedError` matches. on a Router
111831
112094
  // run those mean the PULLFROG wallet is empty and `renderRunError` returns a
111832
112095
  // `BillingError` before ever reaching this list; on a BYOK run the very same
@@ -111957,7 +112220,7 @@ function ossEffortFloor(ctx) {
111957
112220
  const alias = resolveRunAlias(ctx);
111958
112221
  const published = alias?.openRouterEffort ?? alias?.effort;
111959
112222
  if (!published) return 0;
111960
- return rungPosition({ rung: "high", published }) ?? 0;
112223
+ return rungPosition({ rung: SUBSIDY_RUNG, published }) ?? 0;
111961
112224
  }
111962
112225
  function resolveRunEffort(ctx) {
111963
112226
  const configured = ctx.payload.effort !== void 0;
@@ -111978,7 +112241,7 @@ function resolveRunEffort(ctx) {
111978
112241
 
111979
112242
  // utils/skills.ts
111980
112243
  import { spawnSync as spawnSync2 } from "node:child_process";
111981
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
112244
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
111982
112245
  import { tmpdir as tmpdir2 } from "node:os";
111983
112246
  import { dirname, join as join3 } from "node:path";
111984
112247
  import { fileURLToPath } from "node:url";
@@ -111989,7 +112252,7 @@ var import_semver = __toESM(require_semver2(), 1);
111989
112252
  // package.json
111990
112253
  var package_default = {
111991
112254
  name: "pullfrog",
111992
- version: "0.1.66",
112255
+ version: "0.1.68",
111993
112256
  type: "module",
111994
112257
  bin: {
111995
112258
  pullfrog: "dist/cli.mjs",
@@ -112118,7 +112381,7 @@ function resolveSkillPath(name) {
112118
112381
  join3(here, "skills", name, "SKILL.md")
112119
112382
  ];
112120
112383
  for (const candidate of candidates) {
112121
- if (existsSync3(candidate)) return candidate;
112384
+ if (existsSync4(candidate)) return candidate;
112122
112385
  }
112123
112386
  throw new Error(`bundled skill not found: ${name} (looked in ${candidates.join(", ")})`);
112124
112387
  }
@@ -114159,13 +114422,13 @@ var claude = agent({
114159
114422
 
114160
114423
  // agents/codex.ts
114161
114424
  var core2 = __toESM(require_core(), 1);
114162
- import { existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync6 } from "node:fs";
114425
+ import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync6 } from "node:fs";
114163
114426
  import { join as join7 } from "node:path";
114164
114427
  import { performance as performance6 } from "node:perf_hooks";
114165
114428
 
114166
114429
  // utils/codexHome.ts
114167
114430
  import { execFileSync as execFileSync4 } from "node:child_process";
114168
- import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync5 } from "node:fs";
114431
+ import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync5 } from "node:fs";
114169
114432
  import { homedir as homedir2, userInfo } from "node:os";
114170
114433
  import { join as join6 } from "node:path";
114171
114434
  var CODEX_AUTH_ENV = "CODEX_AUTH_JSON";
@@ -114214,7 +114477,7 @@ function installCodexAuth() {
114214
114477
  }
114215
114478
  function writeOpenCodeAuthEntry(params) {
114216
114479
  let existing = {};
114217
- if (existsSync4(params.authPath)) {
114480
+ if (existsSync5(params.authPath)) {
114218
114481
  try {
114219
114482
  const parsed2 = JSON.parse(readFileSync3(params.authPath, "utf8"));
114220
114483
  if (parsed2 && typeof parsed2 === "object") existing = parsed2;
@@ -114531,7 +114794,7 @@ var APPROVAL_MODE_KEY = "default_tools_approval_mode";
114531
114794
  var APPROVE_TOOLS = `${APPROVAL_MODE_KEY} = "approve"`;
114532
114795
  function repoMcpApprovals(repoDir) {
114533
114796
  const path4 = join7(repoDir, ".codex", "config.toml");
114534
- if (!existsSync5(path4)) return [];
114797
+ if (!existsSync6(path4)) return [];
114535
114798
  const out = [];
114536
114799
  let section = [];
114537
114800
  let isServerTable = false;
@@ -178940,7 +179203,7 @@ var pbkdf2Async = promisify(pbkdf2);
178940
179203
 
178941
179204
  // utils/browser.ts
178942
179205
  import { execFileSync as execFileSync5, spawnSync as spawnSync4 } from "node:child_process";
178943
- import { existsSync as existsSync6 } from "node:fs";
179206
+ import { existsSync as existsSync7 } from "node:fs";
178944
179207
  import { dirname as dirname2 } from "node:path";
178945
179208
  var CHROME_PATHS = ["/usr/bin/google-chrome-stable"];
178946
179209
  var systemChromePath;
@@ -178949,7 +179212,7 @@ function findSystemChromePath() {
178949
179212
  return systemChromePath || void 0;
178950
179213
  }
178951
179214
  for (const p of CHROME_PATHS) {
178952
- if (existsSync6(p)) {
179215
+ if (existsSync7(p)) {
178953
179216
  systemChromePath = p;
178954
179217
  log2.info(`found system chrome: ${p}`);
178955
179218
  return p;
@@ -182620,7 +182883,7 @@ var describeExpressionOptions = {
182620
182883
  };
182621
182884
  var expressBranches = (expressions) => describeBranches(expressions, describeExpressionOptions);
182622
182885
  var describeBranches = (descriptions, opts) => {
182623
- const delimiter2 = opts?.delimiter ?? ", ";
182886
+ const delimiter3 = opts?.delimiter ?? ", ";
182624
182887
  const finalDelimiter = opts?.finalDelimiter ?? " or ";
182625
182888
  if (descriptions.length === 0)
182626
182889
  return "never";
@@ -182631,7 +182894,7 @@ var describeBranches = (descriptions, opts) => {
182631
182894
  const seen = {};
182632
182895
  const unique = descriptions.filter((s) => seen[s] ? false : seen[s] = true);
182633
182896
  const last = unique.pop();
182634
- return `${unique.join(delimiter2)}${unique.length ? finalDelimiter : ""}${last}`;
182897
+ return `${unique.join(delimiter3)}${unique.length ? finalDelimiter : ""}${last}`;
182635
182898
  };
182636
182899
  var intersectBranches = (l2, r2, ctx) => {
182637
182900
  const batchesByR = r2.map(() => []);
@@ -193349,7 +193612,7 @@ function isValidUnquotedKey(key) {
193349
193612
  function isIdentifierSegment(key) {
193350
193613
  return /^[A-Z_]\w*$/i.test(key);
193351
193614
  }
193352
- function isSafeUnquoted(value2, delimiter2 = DEFAULT_DELIMITER) {
193615
+ function isSafeUnquoted(value2, delimiter3 = DEFAULT_DELIMITER) {
193353
193616
  if (!value2) return false;
193354
193617
  if (value2 !== value2.trim()) return false;
193355
193618
  if (isBooleanOrNullLiteral(value2) || isNumericLike(value2)) return false;
@@ -193357,7 +193620,7 @@ function isSafeUnquoted(value2, delimiter2 = DEFAULT_DELIMITER) {
193357
193620
  if (value2.includes('"') || value2.includes("\\")) return false;
193358
193621
  if (/[[\]{}]/.test(value2)) return false;
193359
193622
  if (/[\n\r\t]/.test(value2)) return false;
193360
- if (value2.includes(delimiter2)) return false;
193623
+ if (value2.includes(delimiter3)) return false;
193361
193624
  if (value2.startsWith(LIST_ITEM_MARKER)) return false;
193362
193625
  return true;
193363
193626
  }
@@ -193408,33 +193671,33 @@ function collectSingleKeyChain(startKey, startValue, maxDepth) {
193408
193671
  function buildFoldedKey(segments) {
193409
193672
  return segments.join(DOT);
193410
193673
  }
193411
- function encodePrimitive(value2, delimiter2) {
193674
+ function encodePrimitive(value2, delimiter3) {
193412
193675
  if (value2 === null) return NULL_LITERAL;
193413
193676
  if (typeof value2 === "boolean") return String(value2);
193414
193677
  if (typeof value2 === "number") return String(value2);
193415
- return encodeStringLiteral(value2, delimiter2);
193678
+ return encodeStringLiteral(value2, delimiter3);
193416
193679
  }
193417
- function encodeStringLiteral(value2, delimiter2 = DEFAULT_DELIMITER) {
193418
- if (isSafeUnquoted(value2, delimiter2)) return value2;
193680
+ function encodeStringLiteral(value2, delimiter3 = DEFAULT_DELIMITER) {
193681
+ if (isSafeUnquoted(value2, delimiter3)) return value2;
193419
193682
  return `${DOUBLE_QUOTE}${escapeString(value2)}${DOUBLE_QUOTE}`;
193420
193683
  }
193421
193684
  function encodeKey(key) {
193422
193685
  if (isValidUnquotedKey(key)) return key;
193423
193686
  return `${DOUBLE_QUOTE}${escapeString(key)}${DOUBLE_QUOTE}`;
193424
193687
  }
193425
- function encodeAndJoinPrimitives(values, delimiter2 = DEFAULT_DELIMITER) {
193426
- return values.map((v) => encodePrimitive(v, delimiter2)).join(delimiter2);
193688
+ function encodeAndJoinPrimitives(values, delimiter3 = DEFAULT_DELIMITER) {
193689
+ return values.map((v) => encodePrimitive(v, delimiter3)).join(delimiter3);
193427
193690
  }
193428
193691
  function formatHeader(length, options) {
193429
193692
  const key = options?.key;
193430
193693
  const fields = options?.fields;
193431
- const delimiter2 = options?.delimiter ?? COMMA2;
193694
+ const delimiter3 = options?.delimiter ?? COMMA2;
193432
193695
  let header = "";
193433
193696
  if (key) header += encodeKey(key);
193434
- header += `[${length}${delimiter2 !== DEFAULT_DELIMITER ? delimiter2 : ""}]`;
193697
+ header += `[${length}${delimiter3 !== DEFAULT_DELIMITER ? delimiter3 : ""}]`;
193435
193698
  if (fields) {
193436
193699
  const quotedFields = fields.map((f) => encodeKey(f));
193437
- header += `{${quotedFields.join(delimiter2)}}`;
193700
+ header += `{${quotedFields.join(delimiter3)}}`;
193438
193701
  }
193439
193702
  header += ":";
193440
193703
  return header;
@@ -193527,12 +193790,12 @@ function* encodeArrayOfArraysAsListItemsLines(prefix, values, depth, options) {
193527
193790
  yield indentedListItem(depth + 1, arrayLine, options.indent);
193528
193791
  }
193529
193792
  }
193530
- function encodeInlineArrayLine(values, delimiter2, prefix) {
193793
+ function encodeInlineArrayLine(values, delimiter3, prefix) {
193531
193794
  const header = formatHeader(values.length, {
193532
193795
  key: prefix,
193533
- delimiter: delimiter2
193796
+ delimiter: delimiter3
193534
193797
  });
193535
- const joinedValue = encodeAndJoinPrimitives(values, delimiter2);
193798
+ const joinedValue = encodeAndJoinPrimitives(values, delimiter3);
193536
193799
  if (values.length === 0) return header;
193537
193800
  return `${header} ${joinedValue}`;
193538
193801
  }
@@ -194600,6 +194863,9 @@ function formatModelLabel(params) {
194600
194863
  if (params.fallbackFrom) {
194601
194864
  return `${base} (credentials for ${providerDisplayName(params.fallbackFrom)} were rejected by the provider)`;
194602
194865
  }
194866
+ if (params.clamped?.reason === "trial") {
194867
+ return `${base} (free trial)`;
194868
+ }
194603
194869
  if (params.clamped) {
194604
194870
  const target = isAutoTier(params.clamped.from) ? "the intelligent tier" : `\`${resolveDisplayAlias(params.clamped.from)?.displayName ?? params.clamped.from}\``;
194605
194871
  return params.clamped.reason === "card" ? `${base} (${target} needs a [card on file](https://docs.pullfrog.com/models))` : `${base} (${target} needs a [provider key](https://docs.pullfrog.com/models) \u2014 no Router support yet)`;
@@ -194609,6 +194875,15 @@ function formatModelLabel(params) {
194609
194875
  }
194610
194876
  return base;
194611
194877
  }
194878
+ function buildTrialDisclosure(owner) {
194879
+ const consoleUrl2 = owner ? `https://pullfrog.com/console/${owner}` : "https://pullfrog.com/console";
194880
+ return [
194881
+ "---",
194882
+ "",
194883
+ "> [!IMPORTANT]",
194884
+ `> **This run used the free trial model.** \`DeepSeek Flash\` is fast and cheap \u2014 expect lighter work than a frontier model. [Connect a subscription or API key \u2192](${consoleUrl2})`
194885
+ ].join("\n");
194886
+ }
194612
194887
  function buildPullfrogFooter(params) {
194613
194888
  const parts = [];
194614
194889
  if (params.shaPinned) {
@@ -194641,10 +194916,13 @@ function buildPullfrogFooter(params) {
194641
194916
  );
194642
194917
  }
194643
194918
  const allParts = [...parts, "[\u{1D54F}](https://x.com/pullfrogai)"];
194919
+ const disclosure = params.clamped?.reason === "trial" ? `${buildTrialDisclosure(params.owner)}
194920
+
194921
+ ` : "";
194644
194922
  return `
194645
194923
 
194646
194924
  ${PULLFROG_DIVIDER}
194647
- <sup>${FROG_LOGO}&nbsp;&nbsp;\uFF5C ${allParts.join(" \uFF5C ")}</sup>`;
194925
+ ${disclosure}<sup>${FROG_LOGO}&nbsp;&nbsp;\uFF5C ${allParts.join(" \uFF5C ")}</sup>`;
194648
194926
  }
194649
194927
  function stripExistingFooter(body) {
194650
194928
  const dividerIndex = body.indexOf(PULLFROG_DIVIDER);
@@ -194798,7 +195076,8 @@ function buildCommentFooter(ctx, customParts) {
194798
195076
  clamped: ctx.toolState.modelClamped,
194799
195077
  unselectedProxyDefault: ctx.toolState.unselectedProxyDefault,
194800
195078
  shaPinned: ctx.toolState.shaPinned,
194801
- oss: ctx.oss
195079
+ oss: ctx.oss,
195080
+ owner: ctx.repo.owner
194802
195081
  });
194803
195082
  }
194804
195083
  function buildImplementPlanLink(ctx, issueNumber, commentId) {
@@ -195797,7 +196076,8 @@ async function createAndSubmitWithFooter(ctx, params, opts) {
195797
196076
  clamped: ctx.toolState.modelClamped,
195798
196077
  unselectedProxyDefault: ctx.toolState.unselectedProxyDefault,
195799
196078
  shaPinned: ctx.toolState.shaPinned,
195800
- oss: ctx.oss
196079
+ oss: ctx.oss,
196080
+ owner: ctx.repo.owner
195801
196081
  });
195802
196082
  return await ctx.octokit.rest.pulls.submitReview({
195803
196083
  owner: params.owner,
@@ -196951,14 +197231,14 @@ function ReopenCurrentTool(ctx) {
196951
197231
  import { performance as performance9 } from "node:perf_hooks";
196952
197232
 
196953
197233
  // prep/installNodeDependencies.ts
196954
- import { existsSync as existsSync8 } from "node:fs";
197234
+ import { existsSync as existsSync9 } from "node:fs";
196955
197235
  import { join as join19 } from "node:path";
196956
197236
 
196957
197237
  // utils/packageManager.ts
196958
197238
  var import_semver2 = __toESM(require_semver2(), 1);
196959
- import { existsSync as existsSync7 } from "node:fs";
197239
+ import { existsSync as existsSync8 } from "node:fs";
196960
197240
  import { mkdir, readFile as readFile2 } from "node:fs/promises";
196961
- import { delimiter, join as join18 } from "node:path";
197241
+ import { delimiter as delimiter2, join as join18 } from "node:path";
196962
197242
  var SUPPORTED_NAMES = ["npm", "pnpm", "yarn", "bun"];
196963
197243
  var COREPACK_MANAGED = ["pnpm", "yarn"];
196964
197244
  function isSupported(name) {
@@ -196997,7 +197277,7 @@ function parseDevEnginesField(field) {
196997
197277
  }
196998
197278
  async function resolvePackageManagerSpec(cwd) {
196999
197279
  const pkgPath = join18(cwd, "package.json");
197000
- if (!existsSync7(pkgPath)) return null;
197280
+ if (!existsSync8(pkgPath)) return null;
197001
197281
  let pkg;
197002
197282
  try {
197003
197283
  pkg = JSON.parse(await readFile2(pkgPath, "utf-8"));
@@ -197085,7 +197365,7 @@ async function ensurePackageManager(params) {
197085
197365
  );
197086
197366
  return false;
197087
197367
  }
197088
- process.env.PATH = `${params.binDir}${delimiter}${process.env.PATH ?? ""}`;
197368
+ process.env.PATH = `${params.binDir}${delimiter2}${process.env.PATH ?? ""}`;
197089
197369
  const prepare = await runCorepack(["prepare", `${spec.name}@${spec.version}`, "--activate"]);
197090
197370
  if (prepare.exitCode !== 0) {
197091
197371
  log2.warning(
@@ -197152,7 +197432,7 @@ var installNodeDependencies = {
197152
197432
  name: "installNodeDependencies",
197153
197433
  shouldRun: () => {
197154
197434
  const packageJsonPath = join19(process.cwd(), "package.json");
197155
- return existsSync8(packageJsonPath);
197435
+ return existsSync9(packageJsonPath);
197156
197436
  },
197157
197437
  run: async (options) => {
197158
197438
  const declared = await resolvePackageManagerSpec(process.cwd());
@@ -197246,14 +197526,14 @@ ${errorMessage}`]
197246
197526
  };
197247
197527
 
197248
197528
  // prep/installPythonDependencies.ts
197249
- import { existsSync as existsSync9, readFileSync as readFileSync6 } from "node:fs";
197529
+ import { existsSync as existsSync10, readFileSync as readFileSync6 } from "node:fs";
197250
197530
  import { join as join20 } from "node:path";
197251
197531
  function declaresBuildSystem(path4) {
197252
197532
  return /^\s*\[\s*build-system\s*\]/m.test(readFileSync6(path4, "utf8"));
197253
197533
  }
197254
197534
  function configApplies(config3, cwd) {
197255
197535
  const path4 = join20(cwd, config3.file);
197256
- return existsSync9(path4) && (config3.applies?.(path4) ?? true);
197536
+ return existsSync10(path4) && (config3.applies?.(path4) ?? true);
197257
197537
  }
197258
197538
  var PYTHON_CONFIGS = [
197259
197539
  {
@@ -198558,7 +198838,8 @@ function buildPrBodyWithFooter(ctx, body) {
198558
198838
  clamped: ctx.toolState.modelClamped,
198559
198839
  unselectedProxyDefault: ctx.toolState.unselectedProxyDefault,
198560
198840
  shaPinned: ctx.toolState.shaPinned,
198561
- oss: ctx.oss
198841
+ oss: ctx.oss,
198842
+ owner: ctx.repo.owner
198562
198843
  });
198563
198844
  const bodyWithoutFooter = stripExistingFooter(fixDoubleEscapedString(body));
198564
198845
  return `${bodyWithoutFooter}${footer}`;
@@ -199865,10 +200146,12 @@ function isByokSetupError(text) {
199865
200146
  var SECRETS_UNAVAILABLE_MARKER = "couldn't load your Pullfrog secrets";
199866
200147
  var ROUTER_UNFUNDED_MARKER = "your Pullfrog Router balance is empty";
199867
200148
  var CREDENTIAL_REJECTED_MARKER = "was rejected by its provider";
199868
- function buildKeyError(params) {
199869
- if (params.secretsUnavailable) return buildSecretsUnavailableError(params);
199870
- if (params.routerUnfunded) return buildRouterUnfundedError(params);
199871
- return buildMissingApiKeyError(params);
200149
+ var NoUsableCredentialError = class extends Error {
200150
+ };
200151
+ function throwKeyError(params) {
200152
+ if (params.secretsUnavailable) throw new Error(buildSecretsUnavailableError(params));
200153
+ if (params.routerUnfunded) throw new NoUsableCredentialError(buildRouterUnfundedError(params));
200154
+ throw new NoUsableCredentialError(buildMissingApiKeyError(params));
199872
200155
  }
199873
200156
  function buildRouterUnfundedError(params) {
199874
200157
  const billingUrl = `${getApiUrl()}/console/${params.owner}#billing`;
@@ -200067,61 +200350,55 @@ function validateAgentApiKey(params) {
200067
200350
  validateVertexSetup({ owner: params.owner, name: params.name });
200068
200351
  return;
200069
200352
  }
200070
- validateBedrockSetup({ owner: params.owner, name: params.name });
200071
- return;
200353
+ if (process.env[BEDROCK_MODEL_ID_ENV]?.trim() === params.model) {
200354
+ validateBedrockSetup({ owner: params.owner, name: params.name });
200355
+ return;
200356
+ }
200072
200357
  }
200073
200358
  if (params.agent.name === "opencode") {
200074
200359
  if (params.authorized.has(params.model)) return;
200075
200360
  const reason = getModelsFailure();
200076
200361
  if (reason) throw new Error(reason);
200077
200362
  if (getModelEnvVars(params.model).some(hasEnvVar2)) return;
200078
- throw new Error(
200079
- buildKeyError({
200080
- owner: params.owner,
200081
- name: params.name,
200082
- model: params.model,
200083
- secretsUnavailable: params.secretsUnavailable,
200084
- routerUnfunded: params.routerUnfunded
200085
- })
200086
- );
200087
- }
200088
- if (hasSingleProviderAuth(params.agent.name)) return;
200089
- throw new Error(
200090
- buildKeyError({
200363
+ throwKeyError({
200091
200364
  owner: params.owner,
200092
200365
  name: params.name,
200093
200366
  model: params.model,
200094
200367
  secretsUnavailable: params.secretsUnavailable,
200095
200368
  routerUnfunded: params.routerUnfunded
200096
- })
200097
- );
200369
+ });
200370
+ }
200371
+ if (hasSingleProviderAuth(params.agent.name)) return;
200372
+ throwKeyError({
200373
+ owner: params.owner,
200374
+ name: params.name,
200375
+ model: params.model,
200376
+ secretsUnavailable: params.secretsUnavailable,
200377
+ routerUnfunded: params.routerUnfunded
200378
+ });
200098
200379
  }
200099
200380
  if (params.agent.name === "opencode") {
200100
200381
  if ([...params.authorized].some(modelHasRuntimeAuth)) return;
200101
200382
  const reason = getModelsFailure();
200102
200383
  if (reason) throw new Error(reason);
200103
- throw new Error(
200104
- buildKeyError({
200105
- owner: params.owner,
200106
- name: params.name,
200107
- secretsUnavailable: params.secretsUnavailable,
200108
- routerUnfunded: params.routerUnfunded
200109
- })
200110
- );
200111
- }
200112
- if (hasSingleProviderAuth(params.agent.name)) return;
200113
- throw new Error(
200114
- buildKeyError({
200384
+ throwKeyError({
200115
200385
  owner: params.owner,
200116
200386
  name: params.name,
200117
200387
  secretsUnavailable: params.secretsUnavailable,
200118
200388
  routerUnfunded: params.routerUnfunded
200119
- })
200120
- );
200389
+ });
200390
+ }
200391
+ if (hasSingleProviderAuth(params.agent.name)) return;
200392
+ throwKeyError({
200393
+ owner: params.owner,
200394
+ name: params.name,
200395
+ secretsUnavailable: params.secretsUnavailable,
200396
+ routerUnfunded: params.routerUnfunded
200397
+ });
200121
200398
  }
200122
200399
  function isApiKeyAuthError(text) {
200123
200400
  if (!text) return false;
200124
- return text.includes(MISSING_KEY_MARKER) || /Invalid API key/i.test(text) || /Incorrect API key provided/i.test(text) || /\bUser not found\b/i.test(text) || /\bInvalid authentication\b/i.test(text) || /authentication_error/i.test(text) || /Invalid bearer token/i.test(text) || /api_error_status\s*=\s*401/i.test(text) || /API Error:\s*401/i.test(text) || /Failed to authenticate\. API Error:/i.test(text) || /Your api key:.*is invalid/i.test(text) || isMalformedKeyError(text) || isClaudeSubscriptionDisabledError(text) || isClaudeSessionLimitError(text) || isOAuthCredentialExpiredError(text);
200401
+ return text.includes(MISSING_KEY_MARKER) || /Invalid API key/i.test(text) || /Incorrect API key provided/i.test(text) || /\bUser not found\b/i.test(text) || /\bInvalid authentication\b/i.test(text) || /authentication_error/i.test(text) || /Invalid bearer token/i.test(text) || /api_error_status\s*=\s*401/i.test(text) || /API Error:\s*401/i.test(text) || /Failed to authenticate\. API Error:/i.test(text) || /Your api key:.*is invalid/i.test(text) || isMalformedKeyError(text) || isClaudeSubscriptionDisabledError(text) || isKimiPlanTierError(text) || isClaudeSessionLimitError(text) || isOAuthCredentialExpiredError(text);
200125
200402
  }
200126
200403
  function isOAuthCredentialExpiredError(text) {
200127
200404
  return (
@@ -200142,6 +200419,9 @@ function isMalformedKeyError(text) {
200142
200419
  function isClaudeSubscriptionDisabledError(text) {
200143
200420
  return /disabled Claude subscription access/i.test(text);
200144
200421
  }
200422
+ function isKimiPlanTierError(text) {
200423
+ return /Your current (?:subscription does not have access to|plan supports only)\b/i.test(text);
200424
+ }
200145
200425
  var CLAUDE_SESSION_LIMIT_PATTERN = /hit your (?:session|weekly|five-hour|Opus|Sonnet|Haiku)\s+limit(?:\s*·\s*resets\s+([^"\n]+))?/i;
200146
200426
  function isClaudeSessionLimitError(text) {
200147
200427
  return CLAUDE_SESSION_LIMIT_PATTERN.test(text);
@@ -200215,6 +200495,13 @@ function formatApiKeyErrorSummary(params) {
200215
200495
  `[Add repo secret \u2192](${githubSecretsUrl}) \xB7 [Model settings \u2192](${settingsUrl}) \xB7 [Setup docs \u2192](https://docs.pullfrog.com/keys) \xB7 [Ask in Discord \u2192](https://discord.gg/8y96raFg8e)`
200216
200496
  ].join("\n");
200217
200497
  }
200498
+ if (isKimiPlanTierError(params.raw)) {
200499
+ return [
200500
+ "**Your Kimi membership doesn't include the model this run asked for.** Kimi K3 needs a Moderato plan or above, and K3's 1M context and Kimi K2 HighSpeed need Allegretto or above. Upgrade the membership, or switch this repo to **Kimi K2**, which every tier includes.",
200501
+ "",
200502
+ `[Kimi plans \u2192](https://www.kimi.com/membership/pricing) \xB7 [Model settings \u2192](${settingsUrl}) \xB7 [Setup docs \u2192](https://docs.pullfrog.com/kimi-code) \xB7 [Ask in Discord \u2192](https://discord.gg/8y96raFg8e)`
200503
+ ].join("\n");
200504
+ }
200218
200505
  if (isOAuthCredentialExpiredError(params.raw)) {
200219
200506
  return [
200220
200507
  `**Your provider OAuth credential has expired or been revoked.** Re-authenticate the provider connection (e.g. \`pullfrog auth claude\` / \`pullfrog auth codex\` / \`pullfrog auth grok\`), then re-trigger the run.`,
@@ -200232,9 +200519,9 @@ function formatApiKeyErrorSummary(params) {
200232
200519
  ].join("\n");
200233
200520
  }
200234
200521
  return [
200235
- `**Your LLM provider API key was rejected.** Rotate the key in your provider dashboard, then update the matching GitHub Actions secret.`,
200522
+ `**Your LLM provider API key was rejected.** Rotate the key in your provider dashboard, then update the copy Pullfrog uses \u2014 wherever you saved it.`,
200236
200523
  "",
200237
- `[Update repo secret \u2192](${githubSecretsUrl}) \xB7 [Model settings \u2192](${settingsUrl}) \xB7 [Setup docs \u2192](https://docs.pullfrog.com/keys) \xB7 [Ask in Discord \u2192](https://discord.gg/8y96raFg8e)`
200524
+ `[Pullfrog secrets \u2192](${getApiUrl()}/console/${params.owner}) \xB7 [Repo secrets \u2192](${githubSecretsUrl}) \xB7 [Model settings \u2192](${settingsUrl}) \xB7 [Setup docs \u2192](https://docs.pullfrog.com/keys) \xB7 [Ask in Discord \u2192](https://discord.gg/8y96raFg8e)`
200238
200525
  ].join("\n");
200239
200526
  }
200240
200527
 
@@ -201460,6 +201747,9 @@ var DebugInput = type.enumerated("disabled", "enabled");
201460
201747
  var JsonPayload = type({
201461
201748
  "~pullfrog": "true",
201462
201749
  version: "string",
201750
+ // the server's run type, forwarded verbatim to run-context. optional so a
201751
+ // payload from an older server build still parses against a newer action.
201752
+ "type?": "string | undefined",
201463
201753
  "model?": "string | undefined",
201464
201754
  "modelExplicit?": "boolean | undefined",
201465
201755
  "effort?": "number | string | undefined",
@@ -201945,7 +202235,8 @@ ${ctx.error}` : ctx.error;
201945
202235
  clamped: ctx.toolState.modelClamped,
201946
202236
  unselectedProxyDefault: ctx.toolState.unselectedProxyDefault,
201947
202237
  shaPinned: ctx.toolState.shaPinned,
201948
- oss: ctx.toolState.oss
202238
+ oss: ctx.toolState.oss,
202239
+ owner: repoContext.owner
201949
202240
  });
201950
202241
  const body = `${formattedError}${footer}`;
201951
202242
  const comment = ctx.toolState.progressComment;
@@ -202028,7 +202319,7 @@ async function mintProxyKey(ctx) {
202028
202319
  }
202029
202320
  }
202030
202321
  async function buildProxyTokenHeaders(ctx) {
202031
- const fundingSource = ctx.oss ? "oss" : "router";
202322
+ const fundingSource = ctx.fundingSource;
202032
202323
  if (ctx.oidcCredentials) {
202033
202324
  const creds = ctx.oidcCredentials;
202034
202325
  const oidcToken = await op(() => fetchIdTokenFromStash(creds), {
@@ -202060,7 +202351,7 @@ async function resolveProxyModel(ctx) {
202060
202351
  const key = await mintProxyKey({
202061
202352
  oidcCredentials: ctx.oidcCredentials,
202062
202353
  repo: ctx.repo,
202063
- oss: ctx.oss
202354
+ fundingSource: ctx.oss ? "oss" : "router"
202064
202355
  });
202065
202356
  if (!key) return;
202066
202357
  process.env.OPENROUTER_API_KEY = key;
@@ -202132,6 +202423,26 @@ async function runProxyResolution(ctx) {
202132
202423
  throw error62;
202133
202424
  }
202134
202425
  }
202426
+ async function resolveTrialFallback(ctx) {
202427
+ if (process.env.PULLFROG_MODEL?.trim()) return false;
202428
+ if (!ctx.oidcCredentials && !isLocalApiUrl()) return false;
202429
+ const key = await mintProxyKey({
202430
+ oidcCredentials: ctx.oidcCredentials,
202431
+ repo: ctx.repo,
202432
+ fundingSource: "trial"
202433
+ });
202434
+ if (!key) return false;
202435
+ process.env.OPENROUTER_API_KEY = key;
202436
+ core9.setSecret(key);
202437
+ ctx.payload.proxyModel = DEFAULT_PROXY_MODEL;
202438
+ ctx.toolState.model = DEFAULT_PROXY_MODEL;
202439
+ ctx.toolState.modelClamped = {
202440
+ from: ctx.configuredModel ?? DEFAULT_PROXY_MODEL,
202441
+ reason: "trial"
202442
+ };
202443
+ log2.info(`\xBB proxy: trial \u2192 ${DEFAULT_PROXY_MODEL}`);
202444
+ return true;
202445
+ }
202135
202446
 
202136
202447
  // utils/prSummary.ts
202137
202448
  import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
@@ -202309,8 +202620,9 @@ async function fetchRunContext(params) {
202309
202620
  if (params.oidcToken) {
202310
202621
  headers["X-GitHub-OIDC-Token"] = params.oidcToken;
202311
202622
  }
202623
+ const query = params.runType ? `?type=${encodeURIComponent(params.runType)}` : "";
202312
202624
  const response = await apiFetch({
202313
- path: `/api/repo/${params.repoContext.owner}/${params.repoContext.name}/run-context`,
202625
+ path: `/api/repo/${params.repoContext.owner}/${params.repoContext.name}/run-context${query}`,
202314
202626
  headers,
202315
202627
  signal: controller.signal
202316
202628
  });
@@ -202347,7 +202659,8 @@ async function fetchRunContext(params) {
202347
202659
  proxyModel: data.proxyModel,
202348
202660
  dbSecrets: data.dbSecrets,
202349
202661
  secretsUnavailable: data.secretsUnavailable,
202350
- routerUnfunded: data.routerUnfunded
202662
+ routerUnfunded: data.routerUnfunded,
202663
+ trialFallback: data.trialFallback
202351
202664
  };
202352
202665
  } catch {
202353
202666
  clearTimeout(timeoutId);
@@ -202388,7 +202701,7 @@ async function resolveRunContextData(params) {
202388
202701
  retries: [100, 500],
202389
202702
  bail: (error62) => !isTransientOctokitError(error62)
202390
202703
  })(),
202391
- fetchRunContext({ token: params.token, repoContext, oidcToken })
202704
+ fetchRunContext({ token: params.token, repoContext, oidcToken, runType: params.runType })
202392
202705
  ]);
202393
202706
  return {
202394
202707
  repo: {
@@ -202406,7 +202719,8 @@ async function resolveRunContextData(params) {
202406
202719
  // a failed mint on a runner that should have been able to mint is the same
202407
202720
  // outcome as the server-side failure: the run never sees stored secrets.
202408
202721
  secretsUnavailable: runContext.secretsUnavailable || !!process.env.ACTIONS_ID_TOKEN_REQUEST_URL && oidcToken === void 0,
202409
- routerUnfunded: runContext.routerUnfunded
202722
+ routerUnfunded: runContext.routerUnfunded,
202723
+ trialFallback: runContext.trialFallback
202410
202724
  };
202411
202725
  }
202412
202726
 
@@ -203486,7 +203800,11 @@ async function main() {
203486
203800
  resolveGit();
203487
203801
  const jobToken = getJobToken();
203488
203802
  const initialOctokit = createOctokit(jobToken);
203489
- const runContext = await resolveRunContextData({ octokit: initialOctokit, token: jobToken });
203803
+ const runContext = await resolveRunContextData({
203804
+ octokit: initialOctokit,
203805
+ token: jobToken,
203806
+ runType: typeof resolvedPromptInput === "string" ? void 0 : resolvedPromptInput.type
203807
+ });
203490
203808
  timer.checkpoint("runContextData");
203491
203809
  const payload = resolvePayload(resolvedPromptInput, runContext.repoSettings);
203492
203810
  const toolState = initToolState({
@@ -203667,7 +203985,7 @@ async function main() {
203667
203985
  }
203668
203986
  const resolvedModel = credentials.kind === "fellBack" ? credentials.replacement : configuredModel;
203669
203987
  vertexCredentials = materializeVertexCredentials({ model: resolvedModel });
203670
- const agent2 = resolveAgent({
203988
+ let agent2 = resolveAgent({
203671
203989
  model: resolvedModel,
203672
203990
  proxyModel: payload.proxyModel,
203673
203991
  // the account opt-in and the canary arm are both admissions to codex, so
@@ -203679,15 +203997,32 @@ async function main() {
203679
203997
  const effectiveModel = payload.proxyModel ?? resolvedModel ?? payload.model;
203680
203998
  toolState.model = effectiveModel;
203681
203999
  if (!payload.proxyModel) {
203682
- validateAgentApiKey({
203683
- agent: agent2,
203684
- model: effectiveModel,
203685
- authorized: getAuthorizedModels(),
203686
- owner: runContext.repo.owner,
203687
- name: runContext.repo.name,
203688
- secretsUnavailable: runContext.secretsUnavailable,
203689
- routerUnfunded: runContext.routerUnfunded
203690
- });
204000
+ try {
204001
+ validateAgentApiKey({
204002
+ agent: agent2,
204003
+ model: effectiveModel,
204004
+ authorized: getAuthorizedModels(),
204005
+ owner: runContext.repo.owner,
204006
+ name: runContext.repo.name,
204007
+ secretsUnavailable: runContext.secretsUnavailable,
204008
+ routerUnfunded: runContext.routerUnfunded
204009
+ });
204010
+ } catch (missingKey) {
204011
+ const funded = runContext.trialFallback && missingKey instanceof NoUsableCredentialError ? await resolveTrialFallback({
204012
+ payload,
204013
+ configuredModel: effectiveModel,
204014
+ oidcCredentials,
204015
+ repo: runContext.repo,
204016
+ toolState
204017
+ }) : false;
204018
+ if (!funded) throw missingKey;
204019
+ agent2 = resolveAgent({
204020
+ model: resolvedModel,
204021
+ proxyModel: payload.proxyModel,
204022
+ codexAgent: runContext.repoSettings.codexAgent || payload.codexArm === true
204023
+ });
204024
+ toolState.agent = agent2.name;
204025
+ }
203691
204026
  }
203692
204027
  await setupGit({
203693
204028
  gitToken: tokenRef.gitToken,
@@ -203860,7 +204195,7 @@ ${instructions.user}` : null,
203860
204195
  });
203861
204196
  if (agentId === "opencode") {
203862
204197
  const pluginDir = join28(process.cwd(), ".opencode", "plugin");
203863
- const hasPlugins = existsSync10(pluginDir) && readdirSync2(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
204198
+ const hasPlugins = existsSync11(pluginDir) && readdirSync2(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
203864
204199
  if (hasPlugins && toolState.dependencyInstallation?.promise) {
203865
204200
  log2.info(
203866
204201
  "\xBB .opencode/plugin/ detected \u2014 awaiting dependency installation before agent start"
@@ -204597,7 +204932,7 @@ function resolveTarget(repo) {
204597
204932
  return { owner: match3[1], repo: match3[2] };
204598
204933
  }
204599
204934
  function cliVersion() {
204600
- const parts = /^(\d+)\.(\d+)\.(\d+)$/.exec("0.1.66");
204935
+ const parts = /^(\d+)\.(\d+)\.(\d+)$/.exec("0.1.68");
204601
204936
  if (!parts) return "0.0.0";
204602
204937
  return `${Number(parts[1])}.${Number(parts[2])}.${Number(parts[3])}`;
204603
204938
  }
@@ -204764,7 +205099,7 @@ async function runCli5(input2) {
204764
205099
  }
204765
205100
 
204766
205101
  // cli.ts
204767
- var VERSION10 = "0.1.66";
205102
+ var VERSION10 = "0.1.68";
204768
205103
  var bin = basename2(process.argv[1] || "");
204769
205104
  var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
204770
205105
  var rawArgs = process.argv.slice(2);