pullfrog 0.1.90 → 0.1.92
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 +455 -158
- package/dist/external.d.ts +1 -1
- package/dist/index.js +490 -153
- package/dist/internal/index.d.ts +1 -1
- package/dist/internal.js +71 -39
- package/dist/models.d.ts +25 -0
- package/dist/toolState.d.ts +0 -3
- package/dist/utils/buildPullfrogFooter.d.ts +2 -9
- package/dist/utils/claudeSubscription.d.ts +1 -0
- package/dist/utils/codexHome.d.ts +6 -2
- package/dist/utils/credentialFallback.d.ts +8 -16
- package/dist/utils/credentialPool.d.ts +18 -0
- package/dist/utils/openCodeModels.d.ts +4 -0
- package/dist/utils/subscriptionCredentials.d.ts +10 -0
- package/dist/utils/subscriptionProbe.d.ts +18 -2
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -84177,7 +84177,7 @@ var require_Element = __commonJS({
|
|
|
84177
84177
|
var FilteredElementList = require_FilteredElementList();
|
|
84178
84178
|
var DOMException2 = require_DOMException();
|
|
84179
84179
|
var DOMTokenList = require_DOMTokenList();
|
|
84180
|
-
var
|
|
84180
|
+
var select3 = require_select();
|
|
84181
84181
|
var ContainerNode = require_ContainerNode();
|
|
84182
84182
|
var ChildNode = require_ChildNode();
|
|
84183
84183
|
var NonDocumentTypeChildNode = require_NonDocumentTypeChildNode();
|
|
@@ -84938,7 +84938,7 @@ var require_Element = __commonJS({
|
|
|
84938
84938
|
this.className = v;
|
|
84939
84939
|
} },
|
|
84940
84940
|
matches: { value: function(selector) {
|
|
84941
|
-
return
|
|
84941
|
+
return select3.matches(this, selector);
|
|
84942
84942
|
} },
|
|
84943
84943
|
closest: { value: function(selector) {
|
|
84944
84944
|
var el = this;
|
|
@@ -84951,10 +84951,10 @@ var require_Element = __commonJS({
|
|
|
84951
84951
|
return null;
|
|
84952
84952
|
} },
|
|
84953
84953
|
querySelector: { value: function(selector) {
|
|
84954
|
-
return
|
|
84954
|
+
return select3(selector, this)[0];
|
|
84955
84955
|
} },
|
|
84956
84956
|
querySelectorAll: { value: function(selector) {
|
|
84957
|
-
var nodes =
|
|
84957
|
+
var nodes = select3(selector, this);
|
|
84958
84958
|
return nodes.item ? nodes : new NodeList(nodes);
|
|
84959
84959
|
} }
|
|
84960
84960
|
});
|
|
@@ -85482,7 +85482,7 @@ var require_DocumentFragment = __commonJS({
|
|
|
85482
85482
|
var NodeList = require_NodeList();
|
|
85483
85483
|
var ContainerNode = require_ContainerNode();
|
|
85484
85484
|
var Element = require_Element();
|
|
85485
|
-
var
|
|
85485
|
+
var select3 = require_select();
|
|
85486
85486
|
var utils = require_utils8();
|
|
85487
85487
|
function DocumentFragment(doc) {
|
|
85488
85488
|
ContainerNode.call(this);
|
|
@@ -85511,7 +85511,7 @@ var require_DocumentFragment = __commonJS({
|
|
|
85511
85511
|
context.isHTML = true;
|
|
85512
85512
|
context.getElementsByTagName = Element.prototype.getElementsByTagName;
|
|
85513
85513
|
context.nextElement = Object.getOwnPropertyDescriptor(Element.prototype, "firstElementChild").get;
|
|
85514
|
-
var nodes =
|
|
85514
|
+
var nodes = select3(selector, context);
|
|
85515
85515
|
return nodes.item ? nodes : new NodeList(nodes);
|
|
85516
85516
|
} },
|
|
85517
85517
|
// Utility methods
|
|
@@ -88626,7 +88626,7 @@ var require_Document = __commonJS({
|
|
|
88626
88626
|
var NodeIterator = require_NodeIterator();
|
|
88627
88627
|
var NodeFilter = require_NodeFilter();
|
|
88628
88628
|
var URL2 = require_URL();
|
|
88629
|
-
var
|
|
88629
|
+
var select3 = require_select();
|
|
88630
88630
|
var events = require_events3();
|
|
88631
88631
|
var xml = require_xmlnames();
|
|
88632
88632
|
var html = require_htmlelts();
|
|
@@ -89187,10 +89187,10 @@ var require_Document = __commonJS({
|
|
|
89187
89187
|
return this._templateDocCache;
|
|
89188
89188
|
} },
|
|
89189
89189
|
querySelector: { value: function(selector) {
|
|
89190
|
-
return
|
|
89190
|
+
return select3(selector, this)[0];
|
|
89191
89191
|
} },
|
|
89192
89192
|
querySelectorAll: { value: function(selector) {
|
|
89193
|
-
var nodes =
|
|
89193
|
+
var nodes = select3(selector, this);
|
|
89194
89194
|
return nodes.item ? nodes : new NodeList(nodes);
|
|
89195
89195
|
} }
|
|
89196
89196
|
});
|
|
@@ -107103,7 +107103,7 @@ var import_picocolors = __toESM(require_picocolors(), 1);
|
|
|
107103
107103
|
// package.json
|
|
107104
107104
|
var package_default = {
|
|
107105
107105
|
name: "pullfrog",
|
|
107106
|
-
version: "0.1.
|
|
107106
|
+
version: "0.1.92",
|
|
107107
107107
|
cliContractVersion: "1",
|
|
107108
107108
|
type: "module",
|
|
107109
107109
|
bin: {
|
|
@@ -107478,7 +107478,10 @@ var subscriptionSchema = external_exports.object({
|
|
|
107478
107478
|
owner: external_exports.string(),
|
|
107479
107479
|
inherited: external_exports.boolean(),
|
|
107480
107480
|
shared: external_exports.boolean(),
|
|
107481
|
-
registeredAt: external_exports.string()
|
|
107481
|
+
registeredAt: external_exports.string(),
|
|
107482
|
+
/** the provider's last verdict on the current value; null = usable or never checked. */
|
|
107483
|
+
status: external_exports.enum(["rejected", "exhausted"]).nullish(),
|
|
107484
|
+
resetAt: external_exports.string().nullish()
|
|
107482
107485
|
});
|
|
107483
107486
|
var subscriptionListSchema = external_exports.object({
|
|
107484
107487
|
subscriptions: external_exports.array(subscriptionSchema),
|
|
@@ -110394,6 +110397,9 @@ function stripProviderPrefix(specifier) {
|
|
|
110394
110397
|
const slashIndex = specifier.indexOf("/");
|
|
110395
110398
|
return slashIndex > 0 ? specifier.slice(slashIndex + 1) : specifier;
|
|
110396
110399
|
}
|
|
110400
|
+
function modelIdLabel(specifier) {
|
|
110401
|
+
return specifier.slice(specifier.lastIndexOf("/") + 1).replace(/^~/, "");
|
|
110402
|
+
}
|
|
110397
110403
|
function getModelEnvVars(slug2) {
|
|
110398
110404
|
const parsed2 = parseModel(slug2);
|
|
110399
110405
|
const providerConfig = providers[parsed2.provider];
|
|
@@ -110796,10 +110802,12 @@ async function preflightClaudeSubscription(params) {
|
|
|
110796
110802
|
if (res.status !== 401 && res.status !== 403 && res.status !== 429)
|
|
110797
110803
|
return { usable: true, status: res.status };
|
|
110798
110804
|
const body = await res.text().catch(() => "");
|
|
110805
|
+
const reset = Number(res.headers.get("anthropic-ratelimit-unified-reset"));
|
|
110799
110806
|
return {
|
|
110800
110807
|
usable: false,
|
|
110801
110808
|
status: res.status,
|
|
110802
|
-
reason: `${res.status}: ${extractApiErrorMessage(body)}
|
|
110809
|
+
reason: `${res.status}: ${extractApiErrorMessage(body)}`,
|
|
110810
|
+
resetAt: res.status === 429 && reset > 0 ? new Date(reset * 1e3) : void 0
|
|
110803
110811
|
};
|
|
110804
110812
|
}
|
|
110805
110813
|
function extractApiErrorMessage(body) {
|
|
@@ -113524,6 +113532,17 @@ import { join as join6 } from "node:path";
|
|
|
113524
113532
|
var CODEX_AUTH_ENV = "CODEX_AUTH_JSON";
|
|
113525
113533
|
var XAI_AUTH_ENV = "GROK_AUTH_JSON";
|
|
113526
113534
|
var installedSubscriptionPaths = /* @__PURE__ */ new Map();
|
|
113535
|
+
function clearInstalledSubscription(name) {
|
|
113536
|
+
const path4 = installedSubscriptionPaths.get(name);
|
|
113537
|
+
if (!path4 || !existsSync5(path4)) return;
|
|
113538
|
+
const value2 = JSON.parse(readFileSync3(path4, "utf8"));
|
|
113539
|
+
if (!value2 || typeof value2 !== "object") return;
|
|
113540
|
+
if (name === "CODEX_AUTH_JSON" && "openai" in value2) delete value2.openai;
|
|
113541
|
+
if (name === "GROK_AUTH_JSON" && "xai" in value2) delete value2.xai;
|
|
113542
|
+
writeFileSync5(path4, `${JSON.stringify(value2)}
|
|
113543
|
+
`, { mode: 384 });
|
|
113544
|
+
installedSubscriptionPaths.delete(name);
|
|
113545
|
+
}
|
|
113527
113546
|
var PULLFROG_DATA_DIR = "/var/lib/pullfrog";
|
|
113528
113547
|
function isRejectedChain(body) {
|
|
113529
113548
|
if (!body.refresh_rejected_at) return false;
|
|
@@ -113543,6 +113562,7 @@ function installCodexAuth() {
|
|
|
113543
113562
|
if (isRejectedChain(body)) return null;
|
|
113544
113563
|
const expiresMs = decodeJwtExpMs(body.tokens.access_token) ?? 0;
|
|
113545
113564
|
const xdgDataHome = resolveDataHome();
|
|
113565
|
+
if (!xdgDataHome) return null;
|
|
113546
113566
|
const opencodeDir = join6(xdgDataHome, "opencode");
|
|
113547
113567
|
const authPath = join6(opencodeDir, "auth.json");
|
|
113548
113568
|
writeOpenCodeAuthEntry({
|
|
@@ -113592,6 +113612,7 @@ function installXaiAuth() {
|
|
|
113592
113612
|
}
|
|
113593
113613
|
if (isXaiRejectedChain(body)) return null;
|
|
113594
113614
|
const xdgDataHome = resolveDataHome();
|
|
113615
|
+
if (!xdgDataHome) return null;
|
|
113595
113616
|
const opencodeDir = join6(xdgDataHome, "opencode");
|
|
113596
113617
|
const authPath = join6(opencodeDir, "auth.json");
|
|
113597
113618
|
writeOpenCodeAuthEntry({
|
|
@@ -113636,17 +113657,23 @@ function installCodexHome() {
|
|
|
113636
113657
|
);
|
|
113637
113658
|
return null;
|
|
113638
113659
|
}
|
|
113639
|
-
const
|
|
113660
|
+
const root = resolveDataHome();
|
|
113661
|
+
if (!root) return null;
|
|
113662
|
+
const codexHome = join6(root, "codex");
|
|
113640
113663
|
const authPath = join6(codexHome, "auth.json");
|
|
113641
113664
|
mkdirSync5(codexHome, { recursive: true });
|
|
113642
113665
|
writeFileSync5(authPath, stringifyCodexAuthBody(body), { mode: 384 });
|
|
113643
113666
|
log2.info(`\xBB installed Codex auth at ${authPath}`);
|
|
113644
113667
|
return { codexHome, authPath, originalRefresh: body.tokens.refresh_token };
|
|
113645
113668
|
}
|
|
113669
|
+
var dataHome;
|
|
113670
|
+
function canInstallSubscription(name) {
|
|
113671
|
+
return name !== CODEX_AUTH_ENV && name !== XAI_AUTH_ENV || resolveDataHome() !== null;
|
|
113672
|
+
}
|
|
113646
113673
|
function resolveDataHome() {
|
|
113647
113674
|
if (process.env.CI !== "true") return join6(homedir2(), ".local", "share");
|
|
113648
|
-
bootstrapPullfrogDataDir();
|
|
113649
|
-
return
|
|
113675
|
+
if (dataHome === void 0) dataHome = bootstrapPullfrogDataDir();
|
|
113676
|
+
return dataHome;
|
|
113650
113677
|
}
|
|
113651
113678
|
function bootstrapPullfrogDataDir() {
|
|
113652
113679
|
const user = userInfo().username;
|
|
@@ -113663,10 +113690,14 @@ function bootstrapPullfrogDataDir() {
|
|
|
113663
113690
|
});
|
|
113664
113691
|
execFileSync4("sudo", ["-n", "chmod", "700", PULLFROG_DATA_DIR], { stdio: "pipe" });
|
|
113665
113692
|
} catch (err) {
|
|
113666
|
-
|
|
113667
|
-
|
|
113693
|
+
log2.warning(
|
|
113694
|
+
`\xBB subscription auth skipped: could not create ${PULLFROG_DATA_DIR} (${err instanceof Error ? err.message : String(err)}). it must live outside the agent sandbox's reach, which needs passwordless sudo on this runner; runs on a model that needs the subscription will report no key.`
|
|
113668
113695
|
);
|
|
113696
|
+
delete process.env[CODEX_AUTH_ENV];
|
|
113697
|
+
delete process.env[XAI_AUTH_ENV];
|
|
113698
|
+
return null;
|
|
113669
113699
|
}
|
|
113700
|
+
return PULLFROG_DATA_DIR;
|
|
113670
113701
|
}
|
|
113671
113702
|
|
|
113672
113703
|
// utils/oauthWriteback.ts
|
|
@@ -113854,8 +113885,8 @@ async function writeBackEntry(apiToken, entry) {
|
|
|
113854
113885
|
}
|
|
113855
113886
|
try {
|
|
113856
113887
|
const poolState = core2.getState("credential_receipts");
|
|
113857
|
-
const
|
|
113858
|
-
const receipt =
|
|
113888
|
+
const receipts2 = external_exports.record(external_exports.string(), external_exports.string()).parse(JSON.parse(poolState || "{}"));
|
|
113889
|
+
const receipt = receipts2[entry.secretName];
|
|
113859
113890
|
if (poolState && !receipt) return;
|
|
113860
113891
|
const response = await apiFetch({
|
|
113861
113892
|
path: receipt ? "/api/runtime/credentials" : "/api/runtime/secret",
|
|
@@ -120200,6 +120231,9 @@ function captureAuthorizedModels(cliPath) {
|
|
|
120200
120231
|
}
|
|
120201
120232
|
log2.debug(`\xBB opencode authorized: ${authorized.size} models`);
|
|
120202
120233
|
}
|
|
120234
|
+
function authorizeModel(model) {
|
|
120235
|
+
getAuthorizedModels().add(model);
|
|
120236
|
+
}
|
|
120203
120237
|
function getAuthorizedModels() {
|
|
120204
120238
|
if (!authorized) {
|
|
120205
120239
|
throw new Error("getAuthorizedModels called before captureAuthorizedModels");
|
|
@@ -134319,8 +134353,8 @@ function liftWireOnlyMaterial(message, kind) {
|
|
|
134319
134353
|
};
|
|
134320
134354
|
}
|
|
134321
134355
|
function codecResultValidator(codec2, method) {
|
|
134322
|
-
const
|
|
134323
|
-
if (!
|
|
134356
|
+
const probe2 = codec2.validateResult(method, void 0);
|
|
134357
|
+
if (!probe2.ok && probe2.reason === "not-in-era") return void 0;
|
|
134324
134358
|
return { "~standard": {
|
|
134325
134359
|
version: 1,
|
|
134326
134360
|
vendor: "mcp-wire-codec",
|
|
@@ -148696,8 +148730,8 @@ function liftWireOnlyMaterial2(message, kind) {
|
|
|
148696
148730
|
};
|
|
148697
148731
|
}
|
|
148698
148732
|
function codecResultValidator2(codec2, method) {
|
|
148699
|
-
const
|
|
148700
|
-
if (!
|
|
148733
|
+
const probe2 = codec2.validateResult(method, void 0);
|
|
148734
|
+
if (!probe2.ok && probe2.reason === "not-in-era") return void 0;
|
|
148701
148735
|
return { "~standard": {
|
|
148702
148736
|
version: 1,
|
|
148703
148737
|
vendor: "mcp-wire-codec",
|
|
@@ -191876,10 +191910,10 @@ function CommitChangesTool(ctx) {
|
|
|
191876
191910
|
}
|
|
191877
191911
|
const requested = new Set(params.files);
|
|
191878
191912
|
const known = new Set(changes.map((c2) => c2.path));
|
|
191879
|
-
const
|
|
191880
|
-
if (
|
|
191913
|
+
const unknown5 = [...requested].filter((p) => !known.has(p));
|
|
191914
|
+
if (unknown5.length > 0) {
|
|
191881
191915
|
throw new Error(
|
|
191882
|
-
`no detected change at: ${
|
|
191916
|
+
`no detected change at: ${unknown5.join(", ")} \u2014 run git status to list changed paths.`
|
|
191883
191917
|
);
|
|
191884
191918
|
}
|
|
191885
191919
|
changes = changes.filter((c2) => requested.has(c2.path));
|
|
@@ -192272,32 +192306,20 @@ function PushTagsTool(ctx) {
|
|
|
192272
192306
|
// utils/buildPullfrogFooter.ts
|
|
192273
192307
|
var PULLFROG_DIVIDER = "<!-- PULLFROG_DIVIDER_DO_NOT_REMOVE_PLZ -->";
|
|
192274
192308
|
var FROG_LOGO = `<a href="https://pullfrog.com"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pullfrog.com/logos/frog-white-full-18px.png"><img src="https://pullfrog.com/logos/frog-green-full-18px.png" width="9px" height="9px" style="vertical-align: middle; " alt="Pullfrog"></picture></a>`;
|
|
192275
|
-
function providerDisplayName(slug2) {
|
|
192276
|
-
try {
|
|
192277
|
-
const key = getModelProvider(slug2);
|
|
192278
|
-
const meta3 = providers[key];
|
|
192279
|
-
return meta3?.displayName ?? key;
|
|
192280
|
-
} catch {
|
|
192281
|
-
return slug2;
|
|
192282
|
-
}
|
|
192283
|
-
}
|
|
192284
192309
|
function formatModelLabel(params) {
|
|
192285
|
-
const
|
|
192310
|
+
const slugAlias = resolveDisplayAlias(params.model);
|
|
192311
|
+
const alias = slugAlias ?? // reverse-lookup: when the caller passes an effective model (proxy or
|
|
192286
192312
|
// resolved target like "openrouter/anthropic/claude-opus-4.7") instead of
|
|
192287
|
-
// a stored alias slug, find the alias whose resolve target matches
|
|
192288
|
-
// still render a friendly display name.
|
|
192313
|
+
// a stored alias slug, find the alias whose resolve target matches.
|
|
192289
192314
|
modelAliases.find((a2) => a2.resolve === params.model || a2.openRouterResolve === params.model);
|
|
192290
|
-
const
|
|
192315
|
+
const modelName = slugAlias?.routing ? slugAlias.displayName : modelIdLabel(slugAlias?.resolve ?? params.model);
|
|
192291
192316
|
if (params.oss) {
|
|
192292
|
-
const ossBase = `\`${
|
|
192317
|
+
const ossBase = `\`${modelName}\` (free via [Pullfrog for OSS](https://pullfrog.com/for-oss))`;
|
|
192293
192318
|
if (params.clamped?.reason !== "oss") return ossBase;
|
|
192294
192319
|
const configured = isAutoTier(params.clamped.from) ? "the intelligent tier" : `\`${resolveDisplayAlias(params.clamped.from)?.displayName ?? params.clamped.from}\``;
|
|
192295
192320
|
return `${ossBase} (${configured} not used \u2014 pick one of the [funded models](https://docs.pullfrog.com/models#pullfrog-for-oss) or add a [provider key](https://docs.pullfrog.com/keys) to run your own)`;
|
|
192296
192321
|
}
|
|
192297
|
-
const base = alias?.isFree ? `\`${
|
|
192298
|
-
if (params.fallbackFrom) {
|
|
192299
|
-
return `${base} (credentials for ${providerDisplayName(params.fallbackFrom)} were rejected by the provider)`;
|
|
192300
|
-
}
|
|
192322
|
+
const base = alias?.isFree ? `\`${modelName}\` (free)` : `\`${modelName}\``;
|
|
192301
192323
|
if (params.clamped?.reason === "trial") {
|
|
192302
192324
|
return `${base} (model usage covered by Pullfrog)`;
|
|
192303
192325
|
}
|
|
@@ -192338,7 +192360,6 @@ function buildPullfrogFooter(params) {
|
|
|
192338
192360
|
parts.push(
|
|
192339
192361
|
`Using ${formatModelLabel({
|
|
192340
192362
|
model: params.model,
|
|
192341
|
-
fallbackFrom: params.fallbackFrom,
|
|
192342
192363
|
clamped: params.clamped,
|
|
192343
192364
|
unselectedProxyDefault: params.unselectedProxyDefault,
|
|
192344
192365
|
oss: params.oss
|
|
@@ -192538,7 +192559,6 @@ function buildCommentFooter(ctx, customParts) {
|
|
|
192538
192559
|
} : void 0,
|
|
192539
192560
|
customParts,
|
|
192540
192561
|
model: ctx.toolState.model,
|
|
192541
|
-
fallbackFrom: ctx.toolState.modelFallback?.from,
|
|
192542
192562
|
clamped: ctx.toolState.modelClamped,
|
|
192543
192563
|
unselectedProxyDefault: ctx.toolState.unselectedProxyDefault,
|
|
192544
192564
|
oss: ctx.oss,
|
|
@@ -193528,7 +193548,6 @@ async function createAndSubmitWithFooter(ctx, params, opts) {
|
|
|
193528
193548
|
workflowRun: ctx.runId ? { owner: ctx.repo.owner, repo: ctx.repo.name, runId: ctx.runId, jobId: ctx.jobId } : void 0,
|
|
193529
193549
|
customParts,
|
|
193530
193550
|
model: ctx.toolState.model,
|
|
193531
|
-
fallbackFrom: ctx.toolState.modelFallback?.from,
|
|
193532
193551
|
clamped: ctx.toolState.modelClamped,
|
|
193533
193552
|
unselectedProxyDefault: ctx.toolState.unselectedProxyDefault,
|
|
193534
193553
|
oss: ctx.oss,
|
|
@@ -195904,7 +195923,6 @@ function buildPrBodyWithFooter(ctx, body) {
|
|
|
195904
195923
|
triggeredBy: true,
|
|
195905
195924
|
workflowRun: ctx.runId ? { owner: ctx.repo.owner, repo: ctx.repo.name, runId: ctx.runId, jobId: ctx.jobId } : void 0,
|
|
195906
195925
|
model: ctx.toolState.model,
|
|
195907
|
-
fallbackFrom: ctx.toolState.modelFallback?.from,
|
|
195908
195926
|
clamped: ctx.toolState.modelClamped,
|
|
195909
195927
|
unselectedProxyDefault: ctx.toolState.unselectedProxyDefault,
|
|
195910
195928
|
oss: ctx.oss,
|
|
@@ -198053,17 +198071,17 @@ async function verifyCredential(params) {
|
|
|
198053
198071
|
const value2 = params.value.trim();
|
|
198054
198072
|
if (value2.length === 0) return "dead";
|
|
198055
198073
|
if (params.envVar === "CLAUDE_CODE_OAUTH_TOKEN") return verifyClaudeSubscription(value2);
|
|
198056
|
-
const
|
|
198057
|
-
if (!
|
|
198058
|
-
const request2 =
|
|
198074
|
+
const probe2 = PROBES[params.envVar];
|
|
198075
|
+
if (!probe2) return "unknown";
|
|
198076
|
+
const request2 = probe2.request(value2);
|
|
198059
198077
|
try {
|
|
198060
198078
|
const response = await fetch(request2.url, {
|
|
198061
198079
|
headers: request2.headers,
|
|
198062
198080
|
signal: AbortSignal.timeout(5e3)
|
|
198063
198081
|
});
|
|
198064
198082
|
if (response.ok) return "alive";
|
|
198065
|
-
if (
|
|
198066
|
-
return
|
|
198083
|
+
if (probe2.deadWhen) {
|
|
198084
|
+
return probe2.deadWhen(response.status, await response.text()) ? "dead" : "unknown";
|
|
198067
198085
|
}
|
|
198068
198086
|
return response.status === 401 ? "dead" : "unknown";
|
|
198069
198087
|
} catch {
|
|
@@ -198094,14 +198112,6 @@ async function checkOne(params) {
|
|
|
198094
198112
|
const verdict = await verifyCredential({ envVar: params.envVar, value: value2 });
|
|
198095
198113
|
return verdict === "dead" ? { severity: "dead", reason: void 0 } : null;
|
|
198096
198114
|
}
|
|
198097
|
-
function pickReplacement(params) {
|
|
198098
|
-
const candidates = modelAliases.filter(
|
|
198099
|
-
(alias) => !alias.hidden && !alias.fallback && params.authorized.has(alias.resolve) && // a model with no known env vars is routable through credentials we can't
|
|
198100
|
-
// see (Codex auth lands as an on-disk auth.json), so it stays a candidate.
|
|
198101
|
-
!envVarsFor(alias.resolve).some((envVar) => params.deadVars.has(envVar))
|
|
198102
|
-
);
|
|
198103
|
-
return (candidates.find((alias) => alias.preferred) ?? candidates[0])?.resolve;
|
|
198104
|
-
}
|
|
198105
198115
|
async function checkConfiguredCredentials(params) {
|
|
198106
198116
|
if (!params.model) return { kind: "ok" };
|
|
198107
198117
|
const present = envVarsFor(params.model).filter(hasEnvVar4);
|
|
@@ -198127,12 +198137,357 @@ async function checkConfiguredCredentials(params) {
|
|
|
198127
198137
|
failed.forEach(drop);
|
|
198128
198138
|
const first = failed[0];
|
|
198129
198139
|
if (!first) return { kind: "ok" };
|
|
198130
|
-
|
|
198131
|
-
|
|
198132
|
-
|
|
198133
|
-
|
|
198140
|
+
return { kind: "dead", credential: first.envVar, reason: first.failure.reason };
|
|
198141
|
+
}
|
|
198142
|
+
|
|
198143
|
+
// utils/normalizeEnv.ts
|
|
198144
|
+
function hasControlCharacter(value2) {
|
|
198145
|
+
return [...value2].some((ch) => {
|
|
198146
|
+
const code = ch.charCodeAt(0);
|
|
198147
|
+
return code < 32 || code === 127;
|
|
198148
|
+
});
|
|
198149
|
+
}
|
|
198150
|
+
function sanitizeSecret(key, value2) {
|
|
198151
|
+
const trimmed = value2.trim();
|
|
198152
|
+
if (trimmed.length === 0) {
|
|
198153
|
+
log2.warning(
|
|
198154
|
+
`\xBB ${key} is whitespace-only \u2014 leaving env var unchanged. check your secret value.`
|
|
198155
|
+
);
|
|
198156
|
+
return null;
|
|
198157
|
+
}
|
|
198158
|
+
if (key.endsWith("_API_KEY") && hasControlCharacter(trimmed)) {
|
|
198159
|
+
log2.warning(
|
|
198160
|
+
`\xBB ${key} contains a line break or control character and cannot be sent as an HTTP header \u2014 leaving env var unchanged. re-save the secret as a single line.`
|
|
198161
|
+
);
|
|
198162
|
+
return null;
|
|
198163
|
+
}
|
|
198164
|
+
if (trimmed !== value2) {
|
|
198165
|
+
log2.warning(
|
|
198166
|
+
`\xBB stripped whitespace from ${key} (whitespace in secret values breaks GitHub Actions log masking)`
|
|
198167
|
+
);
|
|
198168
|
+
}
|
|
198169
|
+
maskSecret(trimmed);
|
|
198170
|
+
return trimmed;
|
|
198171
|
+
}
|
|
198172
|
+
function normalizeEnv() {
|
|
198173
|
+
const upperKeys = /* @__PURE__ */ new Map();
|
|
198174
|
+
for (const key of Object.keys(process.env)) {
|
|
198175
|
+
const upper2 = key.toUpperCase();
|
|
198176
|
+
const existing = upperKeys.get(upper2) || [];
|
|
198177
|
+
existing.push(key);
|
|
198178
|
+
upperKeys.set(upper2, existing);
|
|
198179
|
+
}
|
|
198180
|
+
for (const [upperKey, keys] of upperKeys) {
|
|
198181
|
+
if (keys.length === 1) {
|
|
198182
|
+
const key = keys[0];
|
|
198183
|
+
if (key !== upperKey) {
|
|
198184
|
+
process.env[upperKey] = process.env[key];
|
|
198185
|
+
delete process.env[key];
|
|
198186
|
+
}
|
|
198187
|
+
continue;
|
|
198188
|
+
}
|
|
198189
|
+
const values = keys.map((k2) => process.env[k2]);
|
|
198190
|
+
const uniqueValues = new Set(values);
|
|
198191
|
+
if (uniqueValues.size > 1) {
|
|
198192
|
+
log2.warning(
|
|
198193
|
+
`env var conflict: ${keys.join(", ")} have different values. using uppercase ${upperKey}.`
|
|
198194
|
+
);
|
|
198195
|
+
}
|
|
198196
|
+
const preferredKey = keys.find((k2) => k2 === upperKey) || keys[0];
|
|
198197
|
+
const preferredValue = process.env[preferredKey];
|
|
198198
|
+
for (const key of keys) {
|
|
198199
|
+
delete process.env[key];
|
|
198200
|
+
}
|
|
198201
|
+
process.env[upperKey] = preferredValue;
|
|
198202
|
+
}
|
|
198203
|
+
for (const key of Object.keys(process.env)) {
|
|
198204
|
+
if (!isSensitiveEnvName(key)) continue;
|
|
198205
|
+
const value2 = process.env[key];
|
|
198206
|
+
if (typeof value2 !== "string" || value2.length === 0) continue;
|
|
198207
|
+
const sanitized = sanitizeSecret(key, value2);
|
|
198208
|
+
if (sanitized !== null) process.env[key] = sanitized;
|
|
198209
|
+
}
|
|
198210
|
+
}
|
|
198211
|
+
|
|
198212
|
+
// utils/subscriptionProbe.ts
|
|
198213
|
+
var identitySchema = external_exports.object({
|
|
198214
|
+
email: external_exports.string().email().optional(),
|
|
198215
|
+
sub: external_exports.string().optional(),
|
|
198216
|
+
user_id: external_exports.string().optional(),
|
|
198217
|
+
account_id: external_exports.string().optional(),
|
|
198218
|
+
email_verified: external_exports.boolean().optional()
|
|
198219
|
+
});
|
|
198220
|
+
var codexWindowSchema = external_exports.object({ used_percent: external_exports.number().optional(), reset_at: external_exports.number().optional() }).nullish();
|
|
198221
|
+
var codexUsageSchema = external_exports.object({
|
|
198222
|
+
rate_limit: external_exports.object({
|
|
198223
|
+
allowed: external_exports.boolean(),
|
|
198224
|
+
limit_reached: external_exports.boolean(),
|
|
198225
|
+
primary_window: codexWindowSchema,
|
|
198226
|
+
secondary_window: codexWindowSchema
|
|
198227
|
+
}).optional()
|
|
198228
|
+
});
|
|
198229
|
+
var unknown4 = { status: "unknown" };
|
|
198230
|
+
async function readSubscriptionEndpoint(input) {
|
|
198231
|
+
const headers = new Headers({ authorization: `Bearer ${input.token}` });
|
|
198232
|
+
if (input.accountId) headers.set("ChatGPT-Account-Id", input.accountId);
|
|
198233
|
+
if (input.grokBilling) headers.set("x-xai-token-auth", "xai-grok-cli");
|
|
198234
|
+
return fetch(input.url, {
|
|
198235
|
+
headers,
|
|
198236
|
+
redirect: "error",
|
|
198237
|
+
signal: AbortSignal.timeout(1e4)
|
|
198238
|
+
}).catch(() => null);
|
|
198239
|
+
}
|
|
198240
|
+
async function probeSubscription(input) {
|
|
198241
|
+
if (input.name === "CLAUDE_CODE_OAUTH_TOKEN") {
|
|
198242
|
+
const result = await preflightClaudeSubscription({ token: input.value, model: input.model });
|
|
198243
|
+
if (result.usable)
|
|
198244
|
+
return result.status !== void 0 && result.status < 300 ? { status: "usable" } : unknown4;
|
|
198245
|
+
return result.status === 429 ? { status: "exhausted", detail: result.reason, resetAt: result.resetAt } : { status: "rejected", detail: result.reason };
|
|
198246
|
+
}
|
|
198247
|
+
const codex2 = input.name === "CODEX_AUTH_JSON" ? parseCodexAuthBody(input.value) : null;
|
|
198248
|
+
const grok = input.name === "GROK_AUTH_JSON" ? parseXaiAuthBody(input.value) : null;
|
|
198249
|
+
const auth2 = codex2 ?? grok;
|
|
198250
|
+
if (!auth2) return { status: "rejected", detail: "the stored credential is malformed" };
|
|
198251
|
+
if (auth2.refresh_rejected_at)
|
|
198252
|
+
return { status: "rejected", detail: `refresh rejected at ${auth2.refresh_rejected_at}` };
|
|
198253
|
+
if (grok && input.model)
|
|
198254
|
+
return probeInference({
|
|
198255
|
+
name: "XAI_API_KEY",
|
|
198256
|
+
value: grok.tokens.access_token,
|
|
198257
|
+
model: input.model
|
|
198258
|
+
});
|
|
198259
|
+
const response = await readSubscriptionEndpoint({
|
|
198260
|
+
url: codex2 ? "https://chatgpt.com/backend-api/wham/usage" : "https://cli-chat-proxy.grok.com/v1/billing?format=credits",
|
|
198261
|
+
token: auth2.tokens.access_token,
|
|
198262
|
+
accountId: codex2?.tokens.account_id,
|
|
198263
|
+
grokBilling: !!grok
|
|
198264
|
+
});
|
|
198265
|
+
if (response?.status === 401) return { status: "rejected", detail: "rejected with 401" };
|
|
198266
|
+
if (!response?.ok) return unknown4;
|
|
198267
|
+
const body = await response.json().catch(() => null);
|
|
198268
|
+
if (!codex2) return unknown4;
|
|
198269
|
+
const parsed2 = codexUsageSchema.safeParse(body);
|
|
198270
|
+
const limit = parsed2.success ? parsed2.data.rate_limit : void 0;
|
|
198271
|
+
if (!limit) return unknown4;
|
|
198272
|
+
if (limit.allowed && !limit.limit_reached) return { status: "usable" };
|
|
198273
|
+
const windows = [limit.primary_window, limit.secondary_window];
|
|
198274
|
+
const full = windows.filter((window2) => (window2?.used_percent ?? 0) >= 100);
|
|
198275
|
+
const resets = (full.length ? full : windows).flatMap(
|
|
198276
|
+
(window2) => window2?.reset_at ? [window2.reset_at] : []
|
|
198277
|
+
);
|
|
198278
|
+
return {
|
|
198279
|
+
status: "exhausted",
|
|
198280
|
+
detail: "the ChatGPT plan's usage limit is reached",
|
|
198281
|
+
resetAt: resets.length ? new Date(Math.max(...resets) * 1e3) : void 0
|
|
198282
|
+
};
|
|
198283
|
+
}
|
|
198284
|
+
async function probeInference(input) {
|
|
198285
|
+
const anthropic = input.name === "ANTHROPIC_API_KEY";
|
|
198286
|
+
const openai = input.name === "OPENAI_API_KEY";
|
|
198287
|
+
if (!anthropic && !openai && input.name !== "XAI_API_KEY") {
|
|
198288
|
+
const result = await verifyCredential({ envVar: input.name, value: input.value });
|
|
198289
|
+
return result === "dead" ? { status: "rejected", detail: "rejected by its provider" } : result === "alive" ? { status: "usable" } : unknown4;
|
|
198290
|
+
}
|
|
198291
|
+
const headers = new Headers({ "content-type": "application/json" });
|
|
198292
|
+
if (anthropic) {
|
|
198293
|
+
headers.set("x-api-key", input.value);
|
|
198294
|
+
headers.set("anthropic-version", "2023-06-01");
|
|
198295
|
+
} else headers.set("authorization", `Bearer ${input.value}`);
|
|
198296
|
+
const response = await fetch(
|
|
198297
|
+
anthropic ? "https://api.anthropic.com/v1/messages" : openai ? "https://api.openai.com/v1/responses" : "https://api.x.ai/v1/chat/completions",
|
|
198298
|
+
{
|
|
198299
|
+
method: "POST",
|
|
198300
|
+
headers,
|
|
198301
|
+
body: JSON.stringify(
|
|
198302
|
+
openai ? { model: input.model, input: "Reply OK.", max_output_tokens: 16, store: false } : {
|
|
198303
|
+
model: input.model,
|
|
198304
|
+
messages: [{ role: "user", content: "Reply OK." }],
|
|
198305
|
+
max_tokens: 1
|
|
198306
|
+
}
|
|
198307
|
+
),
|
|
198308
|
+
redirect: "error",
|
|
198309
|
+
signal: AbortSignal.timeout(1e4)
|
|
198310
|
+
}
|
|
198311
|
+
).catch(() => null);
|
|
198312
|
+
if (response?.status === 400 && input.name === "XAI_API_KEY") {
|
|
198313
|
+
const error49 = external_exports.object({ error: external_exports.string() }).safeParse(await response.json().catch(() => null));
|
|
198314
|
+
return error49.success && error49.data.error.startsWith("Incorrect API key provided.") ? { status: "rejected", detail: "rejected with 400: incorrect API key" } : unknown4;
|
|
198315
|
+
}
|
|
198316
|
+
if (response?.status === 429 && openai) {
|
|
198317
|
+
const error49 = external_exports.object({ error: external_exports.object({ code: external_exports.string() }) }).safeParse(await response.json().catch(() => null));
|
|
198318
|
+
return error49.success && error49.data.error.code === "insufficient_quota" ? { status: "exhausted", detail: "refused with 429: insufficient quota", resetAt: void 0 } : unknown4;
|
|
198319
|
+
}
|
|
198320
|
+
await response?.body?.cancel();
|
|
198321
|
+
if (!response) return unknown4;
|
|
198322
|
+
if (response.ok) return { status: "usable" };
|
|
198323
|
+
if (response.status === 401 || response.status === 403)
|
|
198324
|
+
return { status: "rejected", detail: `rejected with ${response.status}` };
|
|
198325
|
+
if (response.status === 402)
|
|
198326
|
+
return { status: "exhausted", detail: "refused with 402", resetAt: void 0 };
|
|
198327
|
+
return unknown4;
|
|
198328
|
+
}
|
|
198329
|
+
|
|
198330
|
+
// utils/credentialPool.ts
|
|
198331
|
+
var receipts = {};
|
|
198332
|
+
var workflowCredentials = {};
|
|
198333
|
+
function saveReceipts() {
|
|
198334
|
+
for (const receipt of Object.values(receipts)) maskSecret(receipt);
|
|
198335
|
+
saveSecretState("credential_receipts", JSON.stringify(receipts));
|
|
198336
|
+
}
|
|
198337
|
+
var select2 = op(
|
|
198338
|
+
async (input) => {
|
|
198339
|
+
const response = await apiFetch({
|
|
198340
|
+
path: "/api/runtime/credentials",
|
|
198341
|
+
method: "POST",
|
|
198342
|
+
headers: {
|
|
198343
|
+
authorization: `Bearer ${input.access.token}`,
|
|
198344
|
+
"content-type": "application/json"
|
|
198345
|
+
},
|
|
198346
|
+
body: JSON.stringify({ id: input.candidate.id }),
|
|
198347
|
+
signal: AbortSignal.timeout(35e3)
|
|
198348
|
+
});
|
|
198349
|
+
if (response.status === 404) return null;
|
|
198350
|
+
if (!response.ok)
|
|
198351
|
+
throw new Error("could not load a configured credential; no fallback was charged", {
|
|
198352
|
+
cause: response.status
|
|
198353
|
+
});
|
|
198354
|
+
return selectedCredentialSchema.parse(await response.json());
|
|
198355
|
+
},
|
|
198356
|
+
{
|
|
198357
|
+
retries: [250, 1e3],
|
|
198358
|
+
bail: (error49) => error49 instanceof Error && typeof error49.cause === "number" && error49.cause >= 400 && error49.cause < 500
|
|
198359
|
+
}
|
|
198360
|
+
);
|
|
198361
|
+
function subscriptionForModel(model) {
|
|
198362
|
+
if (!model.includes("/")) return null;
|
|
198363
|
+
const provider2 = getModelProvider(model);
|
|
198364
|
+
return provider2 === "anthropic" ? "CLAUDE_CODE_OAUTH_TOKEN" : provider2 === "openai" ? "CODEX_AUTH_JSON" : provider2 === "xai" ? "GROK_AUTH_JSON" : null;
|
|
198365
|
+
}
|
|
198366
|
+
async function initializeCredentialPool(input) {
|
|
198367
|
+
const access = input.ctx.credentialAccess;
|
|
198368
|
+
if (!access) return;
|
|
198369
|
+
const model = discoveryModel(input.slug);
|
|
198370
|
+
maskSecret(access.token);
|
|
198371
|
+
const names = /* @__PURE__ */ new Set([
|
|
198372
|
+
...access.candidates.map((candidate) => candidate.name),
|
|
198373
|
+
...subscriptionNameSchema.options,
|
|
198374
|
+
"ANTHROPIC_API_KEY",
|
|
198375
|
+
"OPENAI_API_KEY",
|
|
198376
|
+
"XAI_API_KEY"
|
|
198377
|
+
]);
|
|
198378
|
+
for (const name of names) {
|
|
198379
|
+
const value2 = process.env[name];
|
|
198380
|
+
if (value2 && !input.injected.includes(name)) workflowCredentials[name] = value2;
|
|
198381
|
+
}
|
|
198382
|
+
for (const name of subscriptionNameSchema.options) {
|
|
198383
|
+
if (model && subscriptionForModel(model) !== name) continue;
|
|
198384
|
+
if (workflowCredentials[name]) continue;
|
|
198385
|
+
const candidate = access.candidates.find((item) => item.name === name);
|
|
198386
|
+
if (!candidate) continue;
|
|
198387
|
+
const selected = await select2({ access, candidate }).catch(() => {
|
|
198388
|
+
log2.warning(`\xBB could not load ${name} for model discovery; selection will retry if needed`);
|
|
198389
|
+
return null;
|
|
198390
|
+
});
|
|
198391
|
+
if (!selected) continue;
|
|
198392
|
+
const value2 = sanitizeSecret(name, selected.value);
|
|
198393
|
+
if (!value2) continue;
|
|
198394
|
+
process.env[name] = value2;
|
|
198395
|
+
receipts[name] = selected.receipt;
|
|
198396
|
+
}
|
|
198397
|
+
saveReceipts();
|
|
198398
|
+
}
|
|
198399
|
+
function discoveryModel(slug2) {
|
|
198400
|
+
const value2 = process.env.PULLFROG_MODEL?.trim() || slug2?.trim();
|
|
198401
|
+
if (!value2) return void 0;
|
|
198402
|
+
return resolveCliModel(value2) ?? (value2.includes("/") ? value2 : void 0);
|
|
198403
|
+
}
|
|
198404
|
+
function resolvePoolModel(input) {
|
|
198405
|
+
const agent2 = resolveAgent({
|
|
198406
|
+
model: void 0,
|
|
198407
|
+
proxyModel: void 0,
|
|
198408
|
+
codexAgent: input.codexAgent
|
|
198134
198409
|
});
|
|
198135
|
-
|
|
198410
|
+
if (agent2.name === "opencode") return autoSelectModel();
|
|
198411
|
+
const provider2 = agent2.name === "claude" ? "anthropic" : "openai";
|
|
198412
|
+
return modelAliases.find(
|
|
198413
|
+
(alias) => alias.provider === provider2 && alias.preferred && !alias.hidden && !alias.fallback && !alias.routing
|
|
198414
|
+
)?.resolve;
|
|
198415
|
+
}
|
|
198416
|
+
async function selectConfiguredCredential(input) {
|
|
198417
|
+
const access = input.ctx.credentialAccess;
|
|
198418
|
+
const model = input.model;
|
|
198419
|
+
if (!access || !model?.includes("/") || getProviderGatewayUrl(model)) return false;
|
|
198420
|
+
const subscription = subscriptionForModel(model);
|
|
198421
|
+
const names = getModelEnvVars(model).filter((name) => name !== subscription);
|
|
198422
|
+
if (subscription) names.unshift(subscription);
|
|
198423
|
+
const candidates = access.candidates.filter(
|
|
198424
|
+
(candidate) => names.includes(candidate.name) && canInstallSubscription(candidate.name)
|
|
198425
|
+
);
|
|
198426
|
+
if (!candidates.length) return false;
|
|
198427
|
+
const refused = [];
|
|
198428
|
+
for (const name of names.filter(
|
|
198429
|
+
(item) => workflowCredentials[item] && canInstallSubscription(item)
|
|
198430
|
+
)) {
|
|
198431
|
+
const value2 = workflowCredentials[name];
|
|
198432
|
+
const verdict = await probe({ name, value: value2, model });
|
|
198433
|
+
if (usable(verdict)) {
|
|
198434
|
+
activate({ names, name, value: value2, receipt: void 0, model });
|
|
198435
|
+
return true;
|
|
198436
|
+
}
|
|
198437
|
+
refused.push(`${name} from the workflow: ${verdict.detail}`);
|
|
198438
|
+
log2.info(`\xBB ${name} from the workflow unavailable; trying the next credential`);
|
|
198439
|
+
}
|
|
198440
|
+
for (const candidate of candidates) {
|
|
198441
|
+
const selected = await select2({ access, candidate });
|
|
198442
|
+
if (!selected) continue;
|
|
198443
|
+
const verdict = await probe({ ...selected, model });
|
|
198444
|
+
await reportVerdict({ receipt: selected.receipt, verdict });
|
|
198445
|
+
if (usable(verdict)) {
|
|
198446
|
+
activate({ names, ...selected, model });
|
|
198447
|
+
log2.info(`\xBB selected ${candidate.name} from ${candidate.source} scope`);
|
|
198448
|
+
return true;
|
|
198449
|
+
}
|
|
198450
|
+
refused.push(`${candidate.name} from ${candidate.source} scope: ${verdict.detail}`);
|
|
198451
|
+
log2.info(
|
|
198452
|
+
`\xBB ${candidate.name} from ${candidate.source} scope unavailable; trying the next credential`
|
|
198453
|
+
);
|
|
198454
|
+
}
|
|
198455
|
+
throw new Error(
|
|
198456
|
+
`all configured credentials for ${model} were rejected or exhausted (${refused.join("; ")}); no other model or provider was selected`
|
|
198457
|
+
);
|
|
198458
|
+
}
|
|
198459
|
+
async function probe(input) {
|
|
198460
|
+
const subscription = subscriptionNameSchema.safeParse(input.name);
|
|
198461
|
+
const model = stripProviderPrefix(input.model);
|
|
198462
|
+
return subscription.success ? probeSubscription({ name: subscription.data, value: input.value, model }) : probeInference({ ...input, model });
|
|
198463
|
+
}
|
|
198464
|
+
function usable(verdict) {
|
|
198465
|
+
return verdict.status !== "rejected" && verdict.status !== "exhausted";
|
|
198466
|
+
}
|
|
198467
|
+
async function reportVerdict(input) {
|
|
198468
|
+
if (input.verdict.status === "unknown") return;
|
|
198469
|
+
await apiFetch({
|
|
198470
|
+
path: "/api/runtime/credentials/status",
|
|
198471
|
+
method: "POST",
|
|
198472
|
+
headers: { authorization: `Bearer ${input.receipt}`, "content-type": "application/json" },
|
|
198473
|
+
body: JSON.stringify(input.verdict),
|
|
198474
|
+
signal: AbortSignal.timeout(5e3)
|
|
198475
|
+
}).catch(() => log2.debug("\xBB could not record a credential verdict"));
|
|
198476
|
+
}
|
|
198477
|
+
function activate(input) {
|
|
198478
|
+
for (const name of input.names) {
|
|
198479
|
+
delete process.env[name];
|
|
198480
|
+
delete receipts[name];
|
|
198481
|
+
if (name === "CODEX_AUTH_JSON" || name === "GROK_AUTH_JSON") clearInstalledSubscription(name);
|
|
198482
|
+
}
|
|
198483
|
+
const value2 = sanitizeSecret(input.name, input.value);
|
|
198484
|
+
if (!value2) throw new Error("configured credential was empty");
|
|
198485
|
+
process.env[input.name] = value2;
|
|
198486
|
+
if (input.receipt) receipts[input.name] = input.receipt;
|
|
198487
|
+
installCodexAuth();
|
|
198488
|
+
installXaiAuth();
|
|
198489
|
+
saveReceipts();
|
|
198490
|
+
authorizeModel(input.model);
|
|
198136
198491
|
}
|
|
198137
198492
|
|
|
198138
198493
|
// utils/gitAuthServer.ts
|
|
@@ -198838,75 +199193,6 @@ function buildModelAccessError(input) {
|
|
|
198838
199193
|
return [headline, "", why, "", cta].join("\n");
|
|
198839
199194
|
}
|
|
198840
199195
|
|
|
198841
|
-
// utils/normalizeEnv.ts
|
|
198842
|
-
function hasControlCharacter(value2) {
|
|
198843
|
-
return [...value2].some((ch) => {
|
|
198844
|
-
const code = ch.charCodeAt(0);
|
|
198845
|
-
return code < 32 || code === 127;
|
|
198846
|
-
});
|
|
198847
|
-
}
|
|
198848
|
-
function sanitizeSecret(key, value2) {
|
|
198849
|
-
const trimmed = value2.trim();
|
|
198850
|
-
if (trimmed.length === 0) {
|
|
198851
|
-
log2.warning(
|
|
198852
|
-
`\xBB ${key} is whitespace-only \u2014 leaving env var unchanged. check your secret value.`
|
|
198853
|
-
);
|
|
198854
|
-
return null;
|
|
198855
|
-
}
|
|
198856
|
-
if (key.endsWith("_API_KEY") && hasControlCharacter(trimmed)) {
|
|
198857
|
-
log2.warning(
|
|
198858
|
-
`\xBB ${key} contains a line break or control character and cannot be sent as an HTTP header \u2014 leaving env var unchanged. re-save the secret as a single line.`
|
|
198859
|
-
);
|
|
198860
|
-
return null;
|
|
198861
|
-
}
|
|
198862
|
-
if (trimmed !== value2) {
|
|
198863
|
-
log2.warning(
|
|
198864
|
-
`\xBB stripped whitespace from ${key} (whitespace in secret values breaks GitHub Actions log masking)`
|
|
198865
|
-
);
|
|
198866
|
-
}
|
|
198867
|
-
maskSecret(trimmed);
|
|
198868
|
-
return trimmed;
|
|
198869
|
-
}
|
|
198870
|
-
function normalizeEnv() {
|
|
198871
|
-
const upperKeys = /* @__PURE__ */ new Map();
|
|
198872
|
-
for (const key of Object.keys(process.env)) {
|
|
198873
|
-
const upper2 = key.toUpperCase();
|
|
198874
|
-
const existing = upperKeys.get(upper2) || [];
|
|
198875
|
-
existing.push(key);
|
|
198876
|
-
upperKeys.set(upper2, existing);
|
|
198877
|
-
}
|
|
198878
|
-
for (const [upperKey, keys] of upperKeys) {
|
|
198879
|
-
if (keys.length === 1) {
|
|
198880
|
-
const key = keys[0];
|
|
198881
|
-
if (key !== upperKey) {
|
|
198882
|
-
process.env[upperKey] = process.env[key];
|
|
198883
|
-
delete process.env[key];
|
|
198884
|
-
}
|
|
198885
|
-
continue;
|
|
198886
|
-
}
|
|
198887
|
-
const values = keys.map((k2) => process.env[k2]);
|
|
198888
|
-
const uniqueValues = new Set(values);
|
|
198889
|
-
if (uniqueValues.size > 1) {
|
|
198890
|
-
log2.warning(
|
|
198891
|
-
`env var conflict: ${keys.join(", ")} have different values. using uppercase ${upperKey}.`
|
|
198892
|
-
);
|
|
198893
|
-
}
|
|
198894
|
-
const preferredKey = keys.find((k2) => k2 === upperKey) || keys[0];
|
|
198895
|
-
const preferredValue = process.env[preferredKey];
|
|
198896
|
-
for (const key of keys) {
|
|
198897
|
-
delete process.env[key];
|
|
198898
|
-
}
|
|
198899
|
-
process.env[upperKey] = preferredValue;
|
|
198900
|
-
}
|
|
198901
|
-
for (const key of Object.keys(process.env)) {
|
|
198902
|
-
if (!isSensitiveEnvName(key)) continue;
|
|
198903
|
-
const value2 = process.env[key];
|
|
198904
|
-
if (typeof value2 !== "string" || value2.length === 0) continue;
|
|
198905
|
-
const sanitized = sanitizeSecret(key, value2);
|
|
198906
|
-
if (sanitized !== null) process.env[key] = sanitized;
|
|
198907
|
-
}
|
|
198908
|
-
}
|
|
198909
|
-
|
|
198910
199196
|
// utils/overrides.ts
|
|
198911
199197
|
var DENIED_OVERRIDE_NAMES = /* @__PURE__ */ new Set([
|
|
198912
199198
|
"GITHUB_TOKEN",
|
|
@@ -199477,7 +199763,6 @@ ${ctx.error}` : ctx.error;
|
|
|
199477
199763
|
workflowRun: runId ? { owner: repoContext.owner, repo: repoContext.name, runId } : void 0,
|
|
199478
199764
|
customParts,
|
|
199479
199765
|
model: ctx.toolState.model,
|
|
199480
|
-
fallbackFrom: ctx.toolState.modelFallback?.from,
|
|
199481
199766
|
clamped: ctx.toolState.modelClamped,
|
|
199482
199767
|
unselectedProxyDefault: ctx.toolState.unselectedProxyDefault,
|
|
199483
199768
|
oss: ctx.toolState.oss,
|
|
@@ -199861,7 +200146,8 @@ async function fetchRunContext(params) {
|
|
|
199861
200146
|
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
199862
200147
|
try {
|
|
199863
200148
|
const headers = {
|
|
199864
|
-
Authorization: `Bearer ${params.token}
|
|
200149
|
+
Authorization: `Bearer ${params.token}`,
|
|
200150
|
+
"X-Pullfrog-Credential-Pools": "1"
|
|
199865
200151
|
};
|
|
199866
200152
|
if (params.oidcToken) {
|
|
199867
200153
|
headers["X-GitHub-OIDC-Token"] = params.oidcToken;
|
|
@@ -201158,16 +201444,20 @@ async function main() {
|
|
|
201158
201444
|
const preIntrospectionDirty = repoDir ? await dirtyTrackedPaths({ cwd: repoDir }) : null;
|
|
201159
201445
|
const opencodeCliPath = await agents.opencode.install();
|
|
201160
201446
|
captureBaselineModels(opencodeCliPath);
|
|
201447
|
+
const injected = [];
|
|
201161
201448
|
if (runContext.dbSecrets) {
|
|
201162
201449
|
for (const [key, value2] of Object.entries(runContext.dbSecrets)) {
|
|
201163
201450
|
if (!process.env[key]) {
|
|
201164
201451
|
const sanitized = sanitizeSecret(key, value2);
|
|
201165
|
-
if (sanitized
|
|
201452
|
+
if (sanitized === null) continue;
|
|
201453
|
+
process.env[key] = sanitized;
|
|
201454
|
+
injected.push(key);
|
|
201166
201455
|
}
|
|
201167
201456
|
}
|
|
201168
201457
|
const count = Object.keys(runContext.dbSecrets).length;
|
|
201169
201458
|
if (count > 0) log2.info(`\xBB ${count} db secret(s) loaded`);
|
|
201170
201459
|
}
|
|
201460
|
+
await initializeCredentialPool({ ctx: runContext, slug: payload.model, injected });
|
|
201171
201461
|
installCodexAuth();
|
|
201172
201462
|
installXaiAuth();
|
|
201173
201463
|
captureAuthorizedModels(opencodeCliPath);
|
|
@@ -201241,13 +201531,23 @@ async function main() {
|
|
|
201241
201531
|
}
|
|
201242
201532
|
const gitAuthServer = __using(_stack2, await startGitAuthServer(tmpdir4), true);
|
|
201243
201533
|
setGitAuthServer(gitAuthServer);
|
|
201534
|
+
let resolvedModel = resolveModel({ slug: payload.model });
|
|
201535
|
+
if (!resolvedModel && !payload.proxyModel && runContext.credentialAccess?.candidates.length) {
|
|
201536
|
+
resolvedModel = resolvePoolModel({
|
|
201537
|
+
codexAgent: runContext.repoSettings.codexAgent || payload.codexArm === true
|
|
201538
|
+
});
|
|
201539
|
+
}
|
|
201540
|
+
let poolSelected = !payload.proxyModel && await selectConfiguredCredential({
|
|
201541
|
+
ctx: runContext,
|
|
201542
|
+
model: resolvedModel
|
|
201543
|
+
});
|
|
201244
201544
|
const access = decideModelAccess({
|
|
201245
201545
|
modelExplicit: payload.modelExplicit ?? false,
|
|
201246
201546
|
model: payload.model,
|
|
201247
201547
|
oss: runContext.oss,
|
|
201248
201548
|
proxyActive: !!payload.proxyModel,
|
|
201249
201549
|
subsidyTarget: runContext.proxyModel,
|
|
201250
|
-
resolvedModel
|
|
201550
|
+
resolvedModel,
|
|
201251
201551
|
authorized: getAuthorizedModels()
|
|
201252
201552
|
});
|
|
201253
201553
|
if (access.kind === "error") {
|
|
@@ -201261,15 +201561,19 @@ async function main() {
|
|
|
201261
201561
|
);
|
|
201262
201562
|
}
|
|
201263
201563
|
if (access.kind === "proxy") payload.proxyModel = access.target;
|
|
201264
|
-
if (access.kind === "byok")
|
|
201564
|
+
if (access.kind === "byok") {
|
|
201565
|
+
payload.proxyModel = void 0;
|
|
201566
|
+
if (!poolSelected)
|
|
201567
|
+
poolSelected = await selectConfiguredCredential({
|
|
201568
|
+
ctx: runContext,
|
|
201569
|
+
model: resolvedModel
|
|
201570
|
+
});
|
|
201571
|
+
}
|
|
201265
201572
|
if (runContext.oss && payload.proxyModel) {
|
|
201266
201573
|
payload.effort = ossEffortFloor({ payload });
|
|
201267
201574
|
}
|
|
201268
|
-
|
|
201269
|
-
const credentials = payload.proxyModel ? { kind: "ok" } : await checkConfiguredCredentials({
|
|
201270
|
-
model: configuredModel,
|
|
201271
|
-
authorized: getAuthorizedModels()
|
|
201272
|
-
});
|
|
201575
|
+
if (payload.proxyModel) resolvedModel = void 0;
|
|
201576
|
+
const credentials = payload.proxyModel || poolSelected ? { kind: "ok" } : await checkConfiguredCredentials({ model: resolvedModel });
|
|
201273
201577
|
if (credentials.kind === "dead") {
|
|
201274
201578
|
throw new Error(
|
|
201275
201579
|
buildRejectedCredentialError({
|
|
@@ -201281,13 +201585,6 @@ async function main() {
|
|
|
201281
201585
|
})
|
|
201282
201586
|
);
|
|
201283
201587
|
}
|
|
201284
|
-
if (credentials.kind === "fellBack" && configuredModel) {
|
|
201285
|
-
log2.warning(
|
|
201286
|
-
`\xBB ${credentials.credential} was rejected by its provider \u2014 running ${credentials.replacement} instead of ${configuredModel}`
|
|
201287
|
-
);
|
|
201288
|
-
toolState.modelFallback = { from: configuredModel };
|
|
201289
|
-
}
|
|
201290
|
-
const resolvedModel = credentials.kind === "fellBack" ? credentials.replacement : configuredModel;
|
|
201291
201588
|
vertexCredentials = materializeVertexCredentials({ model: resolvedModel });
|
|
201292
201589
|
let agent2 = resolveAgent({
|
|
201293
201590
|
model: resolvedModel,
|
|
@@ -201597,12 +201894,12 @@ ${instructions.user}` : null,
|
|
|
201597
201894
|
if (payload.timeout === TIMEOUT_DISABLED) {
|
|
201598
201895
|
result = await Promise.race([agentPromise, activityTimeout.promise]);
|
|
201599
201896
|
} else {
|
|
201600
|
-
const
|
|
201601
|
-
if (payload.timeout &&
|
|
201897
|
+
const usable2 = resolveTimeoutMs(payload.timeout);
|
|
201898
|
+
if (payload.timeout && usable2 === null) {
|
|
201602
201899
|
log2.warning(`invalid timeout "${payload.timeout}" (use --notimeout to disable), using 1h`);
|
|
201603
201900
|
}
|
|
201604
|
-
const timeoutMs =
|
|
201605
|
-
const actualTimeout =
|
|
201901
|
+
const timeoutMs = usable2 ?? 36e5;
|
|
201902
|
+
const actualTimeout = usable2 !== null ? payload.timeout : "1h";
|
|
201606
201903
|
let timeoutId;
|
|
201607
201904
|
const timeoutPromise = new Promise((_4, reject) => {
|
|
201608
201905
|
timeoutId = setTimeout(() => {
|
|
@@ -202230,7 +202527,7 @@ function resolveTarget2(repo) {
|
|
|
202230
202527
|
return { owner: match3[1], repo: match3[2] };
|
|
202231
202528
|
}
|
|
202232
202529
|
function cliVersion() {
|
|
202233
|
-
const parts = /^(\d+)\.(\d+)\.(\d+)$/.exec("0.1.
|
|
202530
|
+
const parts = /^(\d+)\.(\d+)\.(\d+)$/.exec("0.1.92");
|
|
202234
202531
|
if (!parts) return "0.0.0";
|
|
202235
202532
|
return `${Number(parts[1])}.${Number(parts[2])}.${Number(parts[3])}`;
|
|
202236
202533
|
}
|
|
@@ -202397,7 +202694,7 @@ async function runCli8(input) {
|
|
|
202397
202694
|
}
|
|
202398
202695
|
|
|
202399
202696
|
// cli.ts
|
|
202400
|
-
var VERSION10 = "0.1.
|
|
202697
|
+
var VERSION10 = "0.1.92";
|
|
202401
202698
|
var bin = basename2(process.argv[1] || "");
|
|
202402
202699
|
var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
|
|
202403
202700
|
var rawArgs = process.argv.slice(2);
|