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/index.js
CHANGED
|
@@ -83904,7 +83904,7 @@ var require_Element = __commonJS({
|
|
|
83904
83904
|
var FilteredElementList = require_FilteredElementList();
|
|
83905
83905
|
var DOMException2 = require_DOMException();
|
|
83906
83906
|
var DOMTokenList = require_DOMTokenList();
|
|
83907
|
-
var
|
|
83907
|
+
var select2 = require_select();
|
|
83908
83908
|
var ContainerNode = require_ContainerNode();
|
|
83909
83909
|
var ChildNode = require_ChildNode();
|
|
83910
83910
|
var NonDocumentTypeChildNode = require_NonDocumentTypeChildNode();
|
|
@@ -84665,7 +84665,7 @@ var require_Element = __commonJS({
|
|
|
84665
84665
|
this.className = v;
|
|
84666
84666
|
} },
|
|
84667
84667
|
matches: { value: function(selector) {
|
|
84668
|
-
return
|
|
84668
|
+
return select2.matches(this, selector);
|
|
84669
84669
|
} },
|
|
84670
84670
|
closest: { value: function(selector) {
|
|
84671
84671
|
var el = this;
|
|
@@ -84678,10 +84678,10 @@ var require_Element = __commonJS({
|
|
|
84678
84678
|
return null;
|
|
84679
84679
|
} },
|
|
84680
84680
|
querySelector: { value: function(selector) {
|
|
84681
|
-
return
|
|
84681
|
+
return select2(selector, this)[0];
|
|
84682
84682
|
} },
|
|
84683
84683
|
querySelectorAll: { value: function(selector) {
|
|
84684
|
-
var nodes =
|
|
84684
|
+
var nodes = select2(selector, this);
|
|
84685
84685
|
return nodes.item ? nodes : new NodeList(nodes);
|
|
84686
84686
|
} }
|
|
84687
84687
|
});
|
|
@@ -85209,7 +85209,7 @@ var require_DocumentFragment = __commonJS({
|
|
|
85209
85209
|
var NodeList = require_NodeList();
|
|
85210
85210
|
var ContainerNode = require_ContainerNode();
|
|
85211
85211
|
var Element = require_Element();
|
|
85212
|
-
var
|
|
85212
|
+
var select2 = require_select();
|
|
85213
85213
|
var utils = require_utils8();
|
|
85214
85214
|
function DocumentFragment(doc) {
|
|
85215
85215
|
ContainerNode.call(this);
|
|
@@ -85238,7 +85238,7 @@ var require_DocumentFragment = __commonJS({
|
|
|
85238
85238
|
context.isHTML = true;
|
|
85239
85239
|
context.getElementsByTagName = Element.prototype.getElementsByTagName;
|
|
85240
85240
|
context.nextElement = Object.getOwnPropertyDescriptor(Element.prototype, "firstElementChild").get;
|
|
85241
|
-
var nodes =
|
|
85241
|
+
var nodes = select2(selector, context);
|
|
85242
85242
|
return nodes.item ? nodes : new NodeList(nodes);
|
|
85243
85243
|
} },
|
|
85244
85244
|
// Utility methods
|
|
@@ -88353,7 +88353,7 @@ var require_Document = __commonJS({
|
|
|
88353
88353
|
var NodeIterator = require_NodeIterator();
|
|
88354
88354
|
var NodeFilter = require_NodeFilter();
|
|
88355
88355
|
var URL2 = require_URL();
|
|
88356
|
-
var
|
|
88356
|
+
var select2 = require_select();
|
|
88357
88357
|
var events = require_events3();
|
|
88358
88358
|
var xml = require_xmlnames();
|
|
88359
88359
|
var html = require_htmlelts();
|
|
@@ -88914,10 +88914,10 @@ var require_Document = __commonJS({
|
|
|
88914
88914
|
return this._templateDocCache;
|
|
88915
88915
|
} },
|
|
88916
88916
|
querySelector: { value: function(selector) {
|
|
88917
|
-
return
|
|
88917
|
+
return select2(selector, this)[0];
|
|
88918
88918
|
} },
|
|
88919
88919
|
querySelectorAll: { value: function(selector) {
|
|
88920
|
-
var nodes =
|
|
88920
|
+
var nodes = select2(selector, this);
|
|
88921
88921
|
return nodes.item ? nodes : new NodeList(nodes);
|
|
88922
88922
|
} }
|
|
88923
88923
|
});
|
|
@@ -104680,6 +104680,9 @@ function stripProviderPrefix(specifier) {
|
|
|
104680
104680
|
const slashIndex = specifier.indexOf("/");
|
|
104681
104681
|
return slashIndex > 0 ? specifier.slice(slashIndex + 1) : specifier;
|
|
104682
104682
|
}
|
|
104683
|
+
function modelIdLabel(specifier) {
|
|
104684
|
+
return specifier.slice(specifier.lastIndexOf("/") + 1).replace(/^~/, "");
|
|
104685
|
+
}
|
|
104683
104686
|
function getModelEnvVars(slug2) {
|
|
104684
104687
|
const parsed2 = parseModel(slug2);
|
|
104685
104688
|
const providerConfig = providers[parsed2.provider];
|
|
@@ -105082,10 +105085,12 @@ async function preflightClaudeSubscription(params) {
|
|
|
105082
105085
|
if (res.status !== 401 && res.status !== 403 && res.status !== 429)
|
|
105083
105086
|
return { usable: true, status: res.status };
|
|
105084
105087
|
const body = await res.text().catch(() => "");
|
|
105088
|
+
const reset = Number(res.headers.get("anthropic-ratelimit-unified-reset"));
|
|
105085
105089
|
return {
|
|
105086
105090
|
usable: false,
|
|
105087
105091
|
status: res.status,
|
|
105088
|
-
reason: `${res.status}: ${extractApiErrorMessage(body)}
|
|
105092
|
+
reason: `${res.status}: ${extractApiErrorMessage(body)}`,
|
|
105093
|
+
resetAt: res.status === 429 && reset > 0 ? new Date(reset * 1e3) : void 0
|
|
105089
105094
|
};
|
|
105090
105095
|
}
|
|
105091
105096
|
function extractApiErrorMessage(body) {
|
|
@@ -105729,7 +105734,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
105729
105734
|
// package.json
|
|
105730
105735
|
var package_default = {
|
|
105731
105736
|
name: "pullfrog",
|
|
105732
|
-
version: "0.1.
|
|
105737
|
+
version: "0.1.92",
|
|
105733
105738
|
cliContractVersion: "1",
|
|
105734
105739
|
type: "module",
|
|
105735
105740
|
bin: {
|
|
@@ -108001,6 +108006,17 @@ function parseXaiAuthBody(raw2) {
|
|
|
108001
108006
|
var CODEX_AUTH_ENV = "CODEX_AUTH_JSON";
|
|
108002
108007
|
var XAI_AUTH_ENV = "GROK_AUTH_JSON";
|
|
108003
108008
|
var installedSubscriptionPaths = /* @__PURE__ */ new Map();
|
|
108009
|
+
function clearInstalledSubscription(name) {
|
|
108010
|
+
const path4 = installedSubscriptionPaths.get(name);
|
|
108011
|
+
if (!path4 || !existsSync4(path4)) return;
|
|
108012
|
+
const value2 = JSON.parse(readFileSync2(path4, "utf8"));
|
|
108013
|
+
if (!value2 || typeof value2 !== "object") return;
|
|
108014
|
+
if (name === "CODEX_AUTH_JSON" && "openai" in value2) delete value2.openai;
|
|
108015
|
+
if (name === "GROK_AUTH_JSON" && "xai" in value2) delete value2.xai;
|
|
108016
|
+
writeFileSync4(path4, `${JSON.stringify(value2)}
|
|
108017
|
+
`, { mode: 384 });
|
|
108018
|
+
installedSubscriptionPaths.delete(name);
|
|
108019
|
+
}
|
|
108004
108020
|
var PULLFROG_DATA_DIR = "/var/lib/pullfrog";
|
|
108005
108021
|
function isRejectedChain(body) {
|
|
108006
108022
|
if (!body.refresh_rejected_at) return false;
|
|
@@ -108020,6 +108036,7 @@ function installCodexAuth() {
|
|
|
108020
108036
|
if (isRejectedChain(body)) return null;
|
|
108021
108037
|
const expiresMs = decodeJwtExpMs(body.tokens.access_token) ?? 0;
|
|
108022
108038
|
const xdgDataHome = resolveDataHome();
|
|
108039
|
+
if (!xdgDataHome) return null;
|
|
108023
108040
|
const opencodeDir = join5(xdgDataHome, "opencode");
|
|
108024
108041
|
const authPath = join5(opencodeDir, "auth.json");
|
|
108025
108042
|
writeOpenCodeAuthEntry({
|
|
@@ -108069,6 +108086,7 @@ function installXaiAuth() {
|
|
|
108069
108086
|
}
|
|
108070
108087
|
if (isXaiRejectedChain(body)) return null;
|
|
108071
108088
|
const xdgDataHome = resolveDataHome();
|
|
108089
|
+
if (!xdgDataHome) return null;
|
|
108072
108090
|
const opencodeDir = join5(xdgDataHome, "opencode");
|
|
108073
108091
|
const authPath = join5(opencodeDir, "auth.json");
|
|
108074
108092
|
writeOpenCodeAuthEntry({
|
|
@@ -108113,17 +108131,23 @@ function installCodexHome() {
|
|
|
108113
108131
|
);
|
|
108114
108132
|
return null;
|
|
108115
108133
|
}
|
|
108116
|
-
const
|
|
108134
|
+
const root = resolveDataHome();
|
|
108135
|
+
if (!root) return null;
|
|
108136
|
+
const codexHome = join5(root, "codex");
|
|
108117
108137
|
const authPath = join5(codexHome, "auth.json");
|
|
108118
108138
|
mkdirSync5(codexHome, { recursive: true });
|
|
108119
108139
|
writeFileSync4(authPath, stringifyCodexAuthBody(body), { mode: 384 });
|
|
108120
108140
|
log.info(`\xBB installed Codex auth at ${authPath}`);
|
|
108121
108141
|
return { codexHome, authPath, originalRefresh: body.tokens.refresh_token };
|
|
108122
108142
|
}
|
|
108143
|
+
var dataHome;
|
|
108144
|
+
function canInstallSubscription(name) {
|
|
108145
|
+
return name !== CODEX_AUTH_ENV && name !== XAI_AUTH_ENV || resolveDataHome() !== null;
|
|
108146
|
+
}
|
|
108123
108147
|
function resolveDataHome() {
|
|
108124
108148
|
if (process.env.CI !== "true") return join5(homedir2(), ".local", "share");
|
|
108125
|
-
bootstrapPullfrogDataDir();
|
|
108126
|
-
return
|
|
108149
|
+
if (dataHome === void 0) dataHome = bootstrapPullfrogDataDir();
|
|
108150
|
+
return dataHome;
|
|
108127
108151
|
}
|
|
108128
108152
|
function bootstrapPullfrogDataDir() {
|
|
108129
108153
|
const user = userInfo().username;
|
|
@@ -108140,10 +108164,14 @@ function bootstrapPullfrogDataDir() {
|
|
|
108140
108164
|
});
|
|
108141
108165
|
execFileSync3("sudo", ["-n", "chmod", "700", PULLFROG_DATA_DIR], { stdio: "pipe" });
|
|
108142
108166
|
} catch (err) {
|
|
108143
|
-
|
|
108144
|
-
|
|
108167
|
+
log.warning(
|
|
108168
|
+
`\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.`
|
|
108145
108169
|
);
|
|
108170
|
+
delete process.env[CODEX_AUTH_ENV];
|
|
108171
|
+
delete process.env[XAI_AUTH_ENV];
|
|
108172
|
+
return null;
|
|
108146
108173
|
}
|
|
108174
|
+
return PULLFROG_DATA_DIR;
|
|
108147
108175
|
}
|
|
108148
108176
|
|
|
108149
108177
|
// utils/oauthWriteback.ts
|
|
@@ -116693,6 +116721,9 @@ function captureAuthorizedModels(cliPath) {
|
|
|
116693
116721
|
}
|
|
116694
116722
|
log.debug(`\xBB opencode authorized: ${authorized.size} models`);
|
|
116695
116723
|
}
|
|
116724
|
+
function authorizeModel(model) {
|
|
116725
|
+
getAuthorizedModels().add(model);
|
|
116726
|
+
}
|
|
116696
116727
|
function getAuthorizedModels() {
|
|
116697
116728
|
if (!authorized) {
|
|
116698
116729
|
throw new Error("getAuthorizedModels called before captureAuthorizedModels");
|
|
@@ -130812,8 +130843,8 @@ function liftWireOnlyMaterial(message, kind) {
|
|
|
130812
130843
|
};
|
|
130813
130844
|
}
|
|
130814
130845
|
function codecResultValidator(codec2, method) {
|
|
130815
|
-
const
|
|
130816
|
-
if (!
|
|
130846
|
+
const probe2 = codec2.validateResult(method, void 0);
|
|
130847
|
+
if (!probe2.ok && probe2.reason === "not-in-era") return void 0;
|
|
130817
130848
|
return { "~standard": {
|
|
130818
130849
|
version: 1,
|
|
130819
130850
|
vendor: "mcp-wire-codec",
|
|
@@ -145189,8 +145220,8 @@ function liftWireOnlyMaterial2(message, kind) {
|
|
|
145189
145220
|
};
|
|
145190
145221
|
}
|
|
145191
145222
|
function codecResultValidator2(codec2, method) {
|
|
145192
|
-
const
|
|
145193
|
-
if (!
|
|
145223
|
+
const probe2 = codec2.validateResult(method, void 0);
|
|
145224
|
+
if (!probe2.ok && probe2.reason === "not-in-era") return void 0;
|
|
145194
145225
|
return { "~standard": {
|
|
145195
145226
|
version: 1,
|
|
145196
145227
|
vendor: "mcp-wire-codec",
|
|
@@ -188369,10 +188400,10 @@ function CommitChangesTool(ctx) {
|
|
|
188369
188400
|
}
|
|
188370
188401
|
const requested = new Set(params.files);
|
|
188371
188402
|
const known = new Set(changes.map((c) => c.path));
|
|
188372
|
-
const
|
|
188373
|
-
if (
|
|
188403
|
+
const unknown5 = [...requested].filter((p) => !known.has(p));
|
|
188404
|
+
if (unknown5.length > 0) {
|
|
188374
188405
|
throw new Error(
|
|
188375
|
-
`no detected change at: ${
|
|
188406
|
+
`no detected change at: ${unknown5.join(", ")} \u2014 run git status to list changed paths.`
|
|
188376
188407
|
);
|
|
188377
188408
|
}
|
|
188378
188409
|
changes = changes.filter((c) => requested.has(c.path));
|
|
@@ -188765,32 +188796,20 @@ function PushTagsTool(ctx) {
|
|
|
188765
188796
|
// utils/buildPullfrogFooter.ts
|
|
188766
188797
|
var PULLFROG_DIVIDER = "<!-- PULLFROG_DIVIDER_DO_NOT_REMOVE_PLZ -->";
|
|
188767
188798
|
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>`;
|
|
188768
|
-
function providerDisplayName(slug2) {
|
|
188769
|
-
try {
|
|
188770
|
-
const key = getModelProvider(slug2);
|
|
188771
|
-
const meta3 = providers[key];
|
|
188772
|
-
return meta3?.displayName ?? key;
|
|
188773
|
-
} catch {
|
|
188774
|
-
return slug2;
|
|
188775
|
-
}
|
|
188776
|
-
}
|
|
188777
188799
|
function formatModelLabel(params) {
|
|
188778
|
-
const
|
|
188800
|
+
const slugAlias = resolveDisplayAlias(params.model);
|
|
188801
|
+
const alias = slugAlias ?? // reverse-lookup: when the caller passes an effective model (proxy or
|
|
188779
188802
|
// resolved target like "openrouter/anthropic/claude-opus-4.7") instead of
|
|
188780
|
-
// a stored alias slug, find the alias whose resolve target matches
|
|
188781
|
-
// still render a friendly display name.
|
|
188803
|
+
// a stored alias slug, find the alias whose resolve target matches.
|
|
188782
188804
|
modelAliases.find((a) => a.resolve === params.model || a.openRouterResolve === params.model);
|
|
188783
|
-
const
|
|
188805
|
+
const modelName = slugAlias?.routing ? slugAlias.displayName : modelIdLabel(slugAlias?.resolve ?? params.model);
|
|
188784
188806
|
if (params.oss) {
|
|
188785
|
-
const ossBase = `\`${
|
|
188807
|
+
const ossBase = `\`${modelName}\` (free via [Pullfrog for OSS](https://pullfrog.com/for-oss))`;
|
|
188786
188808
|
if (params.clamped?.reason !== "oss") return ossBase;
|
|
188787
188809
|
const configured = isAutoTier(params.clamped.from) ? "the intelligent tier" : `\`${resolveDisplayAlias(params.clamped.from)?.displayName ?? params.clamped.from}\``;
|
|
188788
188810
|
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)`;
|
|
188789
188811
|
}
|
|
188790
|
-
const base = alias?.isFree ? `\`${
|
|
188791
|
-
if (params.fallbackFrom) {
|
|
188792
|
-
return `${base} (credentials for ${providerDisplayName(params.fallbackFrom)} were rejected by the provider)`;
|
|
188793
|
-
}
|
|
188812
|
+
const base = alias?.isFree ? `\`${modelName}\` (free)` : `\`${modelName}\``;
|
|
188794
188813
|
if (params.clamped?.reason === "trial") {
|
|
188795
188814
|
return `${base} (model usage covered by Pullfrog)`;
|
|
188796
188815
|
}
|
|
@@ -188831,7 +188850,6 @@ function buildPullfrogFooter(params) {
|
|
|
188831
188850
|
parts.push(
|
|
188832
188851
|
`Using ${formatModelLabel({
|
|
188833
188852
|
model: params.model,
|
|
188834
|
-
fallbackFrom: params.fallbackFrom,
|
|
188835
188853
|
clamped: params.clamped,
|
|
188836
188854
|
unselectedProxyDefault: params.unselectedProxyDefault,
|
|
188837
188855
|
oss: params.oss
|
|
@@ -189031,7 +189049,6 @@ function buildCommentFooter(ctx, customParts) {
|
|
|
189031
189049
|
} : void 0,
|
|
189032
189050
|
customParts,
|
|
189033
189051
|
model: ctx.toolState.model,
|
|
189034
|
-
fallbackFrom: ctx.toolState.modelFallback?.from,
|
|
189035
189052
|
clamped: ctx.toolState.modelClamped,
|
|
189036
189053
|
unselectedProxyDefault: ctx.toolState.unselectedProxyDefault,
|
|
189037
189054
|
oss: ctx.oss,
|
|
@@ -190021,7 +190038,6 @@ async function createAndSubmitWithFooter(ctx, params, opts) {
|
|
|
190021
190038
|
workflowRun: ctx.runId ? { owner: ctx.repo.owner, repo: ctx.repo.name, runId: ctx.runId, jobId: ctx.jobId } : void 0,
|
|
190022
190039
|
customParts,
|
|
190023
190040
|
model: ctx.toolState.model,
|
|
190024
|
-
fallbackFrom: ctx.toolState.modelFallback?.from,
|
|
190025
190041
|
clamped: ctx.toolState.modelClamped,
|
|
190026
190042
|
unselectedProxyDefault: ctx.toolState.unselectedProxyDefault,
|
|
190027
190043
|
oss: ctx.oss,
|
|
@@ -192397,7 +192413,6 @@ function buildPrBodyWithFooter(ctx, body) {
|
|
|
192397
192413
|
triggeredBy: true,
|
|
192398
192414
|
workflowRun: ctx.runId ? { owner: ctx.repo.owner, repo: ctx.repo.name, runId: ctx.runId, jobId: ctx.jobId } : void 0,
|
|
192399
192415
|
model: ctx.toolState.model,
|
|
192400
|
-
fallbackFrom: ctx.toolState.modelFallback?.from,
|
|
192401
192416
|
clamped: ctx.toolState.modelClamped,
|
|
192402
192417
|
unselectedProxyDefault: ctx.toolState.unselectedProxyDefault,
|
|
192403
192418
|
oss: ctx.oss,
|
|
@@ -194546,17 +194561,17 @@ async function verifyCredential(params) {
|
|
|
194546
194561
|
const value2 = params.value.trim();
|
|
194547
194562
|
if (value2.length === 0) return "dead";
|
|
194548
194563
|
if (params.envVar === "CLAUDE_CODE_OAUTH_TOKEN") return verifyClaudeSubscription(value2);
|
|
194549
|
-
const
|
|
194550
|
-
if (!
|
|
194551
|
-
const request2 =
|
|
194564
|
+
const probe2 = PROBES[params.envVar];
|
|
194565
|
+
if (!probe2) return "unknown";
|
|
194566
|
+
const request2 = probe2.request(value2);
|
|
194552
194567
|
try {
|
|
194553
194568
|
const response = await fetch(request2.url, {
|
|
194554
194569
|
headers: request2.headers,
|
|
194555
194570
|
signal: AbortSignal.timeout(5e3)
|
|
194556
194571
|
});
|
|
194557
194572
|
if (response.ok) return "alive";
|
|
194558
|
-
if (
|
|
194559
|
-
return
|
|
194573
|
+
if (probe2.deadWhen) {
|
|
194574
|
+
return probe2.deadWhen(response.status, await response.text()) ? "dead" : "unknown";
|
|
194560
194575
|
}
|
|
194561
194576
|
return response.status === 401 ? "dead" : "unknown";
|
|
194562
194577
|
} catch {
|
|
@@ -194587,14 +194602,6 @@ async function checkOne(params) {
|
|
|
194587
194602
|
const verdict = await verifyCredential({ envVar: params.envVar, value: value2 });
|
|
194588
194603
|
return verdict === "dead" ? { severity: "dead", reason: void 0 } : null;
|
|
194589
194604
|
}
|
|
194590
|
-
function pickReplacement(params) {
|
|
194591
|
-
const candidates = modelAliases.filter(
|
|
194592
|
-
(alias) => !alias.hidden && !alias.fallback && params.authorized.has(alias.resolve) && // a model with no known env vars is routable through credentials we can't
|
|
194593
|
-
// see (Codex auth lands as an on-disk auth.json), so it stays a candidate.
|
|
194594
|
-
!envVarsFor(alias.resolve).some((envVar) => params.deadVars.has(envVar))
|
|
194595
|
-
);
|
|
194596
|
-
return (candidates.find((alias) => alias.preferred) ?? candidates[0])?.resolve;
|
|
194597
|
-
}
|
|
194598
194605
|
async function checkConfiguredCredentials(params) {
|
|
194599
194606
|
if (!params.model) return { kind: "ok" };
|
|
194600
194607
|
const present = envVarsFor(params.model).filter(hasEnvVar4);
|
|
@@ -194620,12 +194627,400 @@ async function checkConfiguredCredentials(params) {
|
|
|
194620
194627
|
failed.forEach(drop);
|
|
194621
194628
|
const first = failed[0];
|
|
194622
194629
|
if (!first) return { kind: "ok" };
|
|
194623
|
-
|
|
194624
|
-
|
|
194625
|
-
|
|
194626
|
-
|
|
194630
|
+
return { kind: "dead", credential: first.envVar, reason: first.failure.reason };
|
|
194631
|
+
}
|
|
194632
|
+
|
|
194633
|
+
// utils/normalizeEnv.ts
|
|
194634
|
+
function hasControlCharacter(value2) {
|
|
194635
|
+
return [...value2].some((ch) => {
|
|
194636
|
+
const code = ch.charCodeAt(0);
|
|
194637
|
+
return code < 32 || code === 127;
|
|
194627
194638
|
});
|
|
194628
|
-
|
|
194639
|
+
}
|
|
194640
|
+
function sanitizeSecret(key, value2) {
|
|
194641
|
+
const trimmed = value2.trim();
|
|
194642
|
+
if (trimmed.length === 0) {
|
|
194643
|
+
log.warning(
|
|
194644
|
+
`\xBB ${key} is whitespace-only \u2014 leaving env var unchanged. check your secret value.`
|
|
194645
|
+
);
|
|
194646
|
+
return null;
|
|
194647
|
+
}
|
|
194648
|
+
if (key.endsWith("_API_KEY") && hasControlCharacter(trimmed)) {
|
|
194649
|
+
log.warning(
|
|
194650
|
+
`\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.`
|
|
194651
|
+
);
|
|
194652
|
+
return null;
|
|
194653
|
+
}
|
|
194654
|
+
if (trimmed !== value2) {
|
|
194655
|
+
log.warning(
|
|
194656
|
+
`\xBB stripped whitespace from ${key} (whitespace in secret values breaks GitHub Actions log masking)`
|
|
194657
|
+
);
|
|
194658
|
+
}
|
|
194659
|
+
maskSecret(trimmed);
|
|
194660
|
+
return trimmed;
|
|
194661
|
+
}
|
|
194662
|
+
function normalizeEnv() {
|
|
194663
|
+
const upperKeys = /* @__PURE__ */ new Map();
|
|
194664
|
+
for (const key of Object.keys(process.env)) {
|
|
194665
|
+
const upper2 = key.toUpperCase();
|
|
194666
|
+
const existing = upperKeys.get(upper2) || [];
|
|
194667
|
+
existing.push(key);
|
|
194668
|
+
upperKeys.set(upper2, existing);
|
|
194669
|
+
}
|
|
194670
|
+
for (const [upperKey, keys] of upperKeys) {
|
|
194671
|
+
if (keys.length === 1) {
|
|
194672
|
+
const key = keys[0];
|
|
194673
|
+
if (key !== upperKey) {
|
|
194674
|
+
process.env[upperKey] = process.env[key];
|
|
194675
|
+
delete process.env[key];
|
|
194676
|
+
}
|
|
194677
|
+
continue;
|
|
194678
|
+
}
|
|
194679
|
+
const values = keys.map((k2) => process.env[k2]);
|
|
194680
|
+
const uniqueValues = new Set(values);
|
|
194681
|
+
if (uniqueValues.size > 1) {
|
|
194682
|
+
log.warning(
|
|
194683
|
+
`env var conflict: ${keys.join(", ")} have different values. using uppercase ${upperKey}.`
|
|
194684
|
+
);
|
|
194685
|
+
}
|
|
194686
|
+
const preferredKey = keys.find((k2) => k2 === upperKey) || keys[0];
|
|
194687
|
+
const preferredValue = process.env[preferredKey];
|
|
194688
|
+
for (const key of keys) {
|
|
194689
|
+
delete process.env[key];
|
|
194690
|
+
}
|
|
194691
|
+
process.env[upperKey] = preferredValue;
|
|
194692
|
+
}
|
|
194693
|
+
for (const key of Object.keys(process.env)) {
|
|
194694
|
+
if (!isSensitiveEnvName(key)) continue;
|
|
194695
|
+
const value2 = process.env[key];
|
|
194696
|
+
if (typeof value2 !== "string" || value2.length === 0) continue;
|
|
194697
|
+
const sanitized = sanitizeSecret(key, value2);
|
|
194698
|
+
if (sanitized !== null) process.env[key] = sanitized;
|
|
194699
|
+
}
|
|
194700
|
+
}
|
|
194701
|
+
|
|
194702
|
+
// utils/subscriptionCredentials.ts
|
|
194703
|
+
var subscriptionNameSchema = external_exports.enum([
|
|
194704
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
194705
|
+
"CODEX_AUTH_JSON",
|
|
194706
|
+
"GROK_AUTH_JSON"
|
|
194707
|
+
]);
|
|
194708
|
+
var subscriptionSchema = external_exports.object({
|
|
194709
|
+
id: external_exports.string(),
|
|
194710
|
+
credentialId: external_exports.string(),
|
|
194711
|
+
name: subscriptionNameSchema,
|
|
194712
|
+
label: external_exports.string(),
|
|
194713
|
+
owner: external_exports.string(),
|
|
194714
|
+
inherited: external_exports.boolean(),
|
|
194715
|
+
shared: external_exports.boolean(),
|
|
194716
|
+
registeredAt: external_exports.string(),
|
|
194717
|
+
/** the provider's last verdict on the current value; null = usable or never checked. */
|
|
194718
|
+
status: external_exports.enum(["rejected", "exhausted"]).nullish(),
|
|
194719
|
+
resetAt: external_exports.string().nullish()
|
|
194720
|
+
});
|
|
194721
|
+
var subscriptionListSchema = external_exports.object({
|
|
194722
|
+
subscriptions: external_exports.array(subscriptionSchema),
|
|
194723
|
+
writable: external_exports.boolean()
|
|
194724
|
+
});
|
|
194725
|
+
var subscriptionInputSchema = external_exports.object({
|
|
194726
|
+
name: subscriptionNameSchema,
|
|
194727
|
+
value: external_exports.string().trim().min(1).refine((value2) => Buffer.byteLength(value2, "utf8") <= 49152),
|
|
194728
|
+
replaceId: external_exports.string().optional()
|
|
194729
|
+
});
|
|
194730
|
+
var credentialCandidateSchema = external_exports.object({
|
|
194731
|
+
id: external_exports.string(),
|
|
194732
|
+
name: external_exports.string(),
|
|
194733
|
+
source: external_exports.enum(["repo", "account", "personal"])
|
|
194734
|
+
});
|
|
194735
|
+
var credentialAccessSchema = external_exports.object({
|
|
194736
|
+
token: external_exports.string(),
|
|
194737
|
+
candidates: external_exports.array(credentialCandidateSchema)
|
|
194738
|
+
});
|
|
194739
|
+
var selectedCredentialSchema = external_exports.object({
|
|
194740
|
+
name: external_exports.string(),
|
|
194741
|
+
value: external_exports.string(),
|
|
194742
|
+
receipt: external_exports.string()
|
|
194743
|
+
});
|
|
194744
|
+
|
|
194745
|
+
// utils/subscriptionProbe.ts
|
|
194746
|
+
var identitySchema = external_exports.object({
|
|
194747
|
+
email: external_exports.string().email().optional(),
|
|
194748
|
+
sub: external_exports.string().optional(),
|
|
194749
|
+
user_id: external_exports.string().optional(),
|
|
194750
|
+
account_id: external_exports.string().optional(),
|
|
194751
|
+
email_verified: external_exports.boolean().optional()
|
|
194752
|
+
});
|
|
194753
|
+
var codexWindowSchema = external_exports.object({ used_percent: external_exports.number().optional(), reset_at: external_exports.number().optional() }).nullish();
|
|
194754
|
+
var codexUsageSchema = external_exports.object({
|
|
194755
|
+
rate_limit: external_exports.object({
|
|
194756
|
+
allowed: external_exports.boolean(),
|
|
194757
|
+
limit_reached: external_exports.boolean(),
|
|
194758
|
+
primary_window: codexWindowSchema,
|
|
194759
|
+
secondary_window: codexWindowSchema
|
|
194760
|
+
}).optional()
|
|
194761
|
+
});
|
|
194762
|
+
var unknown4 = { status: "unknown" };
|
|
194763
|
+
async function readSubscriptionEndpoint(input) {
|
|
194764
|
+
const headers = new Headers({ authorization: `Bearer ${input.token}` });
|
|
194765
|
+
if (input.accountId) headers.set("ChatGPT-Account-Id", input.accountId);
|
|
194766
|
+
if (input.grokBilling) headers.set("x-xai-token-auth", "xai-grok-cli");
|
|
194767
|
+
return fetch(input.url, {
|
|
194768
|
+
headers,
|
|
194769
|
+
redirect: "error",
|
|
194770
|
+
signal: AbortSignal.timeout(1e4)
|
|
194771
|
+
}).catch(() => null);
|
|
194772
|
+
}
|
|
194773
|
+
async function probeSubscription(input) {
|
|
194774
|
+
if (input.name === "CLAUDE_CODE_OAUTH_TOKEN") {
|
|
194775
|
+
const result = await preflightClaudeSubscription({ token: input.value, model: input.model });
|
|
194776
|
+
if (result.usable)
|
|
194777
|
+
return result.status !== void 0 && result.status < 300 ? { status: "usable" } : unknown4;
|
|
194778
|
+
return result.status === 429 ? { status: "exhausted", detail: result.reason, resetAt: result.resetAt } : { status: "rejected", detail: result.reason };
|
|
194779
|
+
}
|
|
194780
|
+
const codex2 = input.name === "CODEX_AUTH_JSON" ? parseCodexAuthBody(input.value) : null;
|
|
194781
|
+
const grok = input.name === "GROK_AUTH_JSON" ? parseXaiAuthBody(input.value) : null;
|
|
194782
|
+
const auth2 = codex2 ?? grok;
|
|
194783
|
+
if (!auth2) return { status: "rejected", detail: "the stored credential is malformed" };
|
|
194784
|
+
if (auth2.refresh_rejected_at)
|
|
194785
|
+
return { status: "rejected", detail: `refresh rejected at ${auth2.refresh_rejected_at}` };
|
|
194786
|
+
if (grok && input.model)
|
|
194787
|
+
return probeInference({
|
|
194788
|
+
name: "XAI_API_KEY",
|
|
194789
|
+
value: grok.tokens.access_token,
|
|
194790
|
+
model: input.model
|
|
194791
|
+
});
|
|
194792
|
+
const response = await readSubscriptionEndpoint({
|
|
194793
|
+
url: codex2 ? "https://chatgpt.com/backend-api/wham/usage" : "https://cli-chat-proxy.grok.com/v1/billing?format=credits",
|
|
194794
|
+
token: auth2.tokens.access_token,
|
|
194795
|
+
accountId: codex2?.tokens.account_id,
|
|
194796
|
+
grokBilling: !!grok
|
|
194797
|
+
});
|
|
194798
|
+
if (response?.status === 401) return { status: "rejected", detail: "rejected with 401" };
|
|
194799
|
+
if (!response?.ok) return unknown4;
|
|
194800
|
+
const body = await response.json().catch(() => null);
|
|
194801
|
+
if (!codex2) return unknown4;
|
|
194802
|
+
const parsed2 = codexUsageSchema.safeParse(body);
|
|
194803
|
+
const limit = parsed2.success ? parsed2.data.rate_limit : void 0;
|
|
194804
|
+
if (!limit) return unknown4;
|
|
194805
|
+
if (limit.allowed && !limit.limit_reached) return { status: "usable" };
|
|
194806
|
+
const windows = [limit.primary_window, limit.secondary_window];
|
|
194807
|
+
const full = windows.filter((window2) => (window2?.used_percent ?? 0) >= 100);
|
|
194808
|
+
const resets = (full.length ? full : windows).flatMap(
|
|
194809
|
+
(window2) => window2?.reset_at ? [window2.reset_at] : []
|
|
194810
|
+
);
|
|
194811
|
+
return {
|
|
194812
|
+
status: "exhausted",
|
|
194813
|
+
detail: "the ChatGPT plan's usage limit is reached",
|
|
194814
|
+
resetAt: resets.length ? new Date(Math.max(...resets) * 1e3) : void 0
|
|
194815
|
+
};
|
|
194816
|
+
}
|
|
194817
|
+
async function probeInference(input) {
|
|
194818
|
+
const anthropic = input.name === "ANTHROPIC_API_KEY";
|
|
194819
|
+
const openai = input.name === "OPENAI_API_KEY";
|
|
194820
|
+
if (!anthropic && !openai && input.name !== "XAI_API_KEY") {
|
|
194821
|
+
const result = await verifyCredential({ envVar: input.name, value: input.value });
|
|
194822
|
+
return result === "dead" ? { status: "rejected", detail: "rejected by its provider" } : result === "alive" ? { status: "usable" } : unknown4;
|
|
194823
|
+
}
|
|
194824
|
+
const headers = new Headers({ "content-type": "application/json" });
|
|
194825
|
+
if (anthropic) {
|
|
194826
|
+
headers.set("x-api-key", input.value);
|
|
194827
|
+
headers.set("anthropic-version", "2023-06-01");
|
|
194828
|
+
} else headers.set("authorization", `Bearer ${input.value}`);
|
|
194829
|
+
const response = await fetch(
|
|
194830
|
+
anthropic ? "https://api.anthropic.com/v1/messages" : openai ? "https://api.openai.com/v1/responses" : "https://api.x.ai/v1/chat/completions",
|
|
194831
|
+
{
|
|
194832
|
+
method: "POST",
|
|
194833
|
+
headers,
|
|
194834
|
+
body: JSON.stringify(
|
|
194835
|
+
openai ? { model: input.model, input: "Reply OK.", max_output_tokens: 16, store: false } : {
|
|
194836
|
+
model: input.model,
|
|
194837
|
+
messages: [{ role: "user", content: "Reply OK." }],
|
|
194838
|
+
max_tokens: 1
|
|
194839
|
+
}
|
|
194840
|
+
),
|
|
194841
|
+
redirect: "error",
|
|
194842
|
+
signal: AbortSignal.timeout(1e4)
|
|
194843
|
+
}
|
|
194844
|
+
).catch(() => null);
|
|
194845
|
+
if (response?.status === 400 && input.name === "XAI_API_KEY") {
|
|
194846
|
+
const error49 = external_exports.object({ error: external_exports.string() }).safeParse(await response.json().catch(() => null));
|
|
194847
|
+
return error49.success && error49.data.error.startsWith("Incorrect API key provided.") ? { status: "rejected", detail: "rejected with 400: incorrect API key" } : unknown4;
|
|
194848
|
+
}
|
|
194849
|
+
if (response?.status === 429 && openai) {
|
|
194850
|
+
const error49 = external_exports.object({ error: external_exports.object({ code: external_exports.string() }) }).safeParse(await response.json().catch(() => null));
|
|
194851
|
+
return error49.success && error49.data.error.code === "insufficient_quota" ? { status: "exhausted", detail: "refused with 429: insufficient quota", resetAt: void 0 } : unknown4;
|
|
194852
|
+
}
|
|
194853
|
+
await response?.body?.cancel();
|
|
194854
|
+
if (!response) return unknown4;
|
|
194855
|
+
if (response.ok) return { status: "usable" };
|
|
194856
|
+
if (response.status === 401 || response.status === 403)
|
|
194857
|
+
return { status: "rejected", detail: `rejected with ${response.status}` };
|
|
194858
|
+
if (response.status === 402)
|
|
194859
|
+
return { status: "exhausted", detail: "refused with 402", resetAt: void 0 };
|
|
194860
|
+
return unknown4;
|
|
194861
|
+
}
|
|
194862
|
+
|
|
194863
|
+
// utils/credentialPool.ts
|
|
194864
|
+
var receipts = {};
|
|
194865
|
+
var workflowCredentials = {};
|
|
194866
|
+
function saveReceipts() {
|
|
194867
|
+
for (const receipt of Object.values(receipts)) maskSecret(receipt);
|
|
194868
|
+
saveSecretState("credential_receipts", JSON.stringify(receipts));
|
|
194869
|
+
}
|
|
194870
|
+
var select = op(
|
|
194871
|
+
async (input) => {
|
|
194872
|
+
const response = await apiFetch({
|
|
194873
|
+
path: "/api/runtime/credentials",
|
|
194874
|
+
method: "POST",
|
|
194875
|
+
headers: {
|
|
194876
|
+
authorization: `Bearer ${input.access.token}`,
|
|
194877
|
+
"content-type": "application/json"
|
|
194878
|
+
},
|
|
194879
|
+
body: JSON.stringify({ id: input.candidate.id }),
|
|
194880
|
+
signal: AbortSignal.timeout(35e3)
|
|
194881
|
+
});
|
|
194882
|
+
if (response.status === 404) return null;
|
|
194883
|
+
if (!response.ok)
|
|
194884
|
+
throw new Error("could not load a configured credential; no fallback was charged", {
|
|
194885
|
+
cause: response.status
|
|
194886
|
+
});
|
|
194887
|
+
return selectedCredentialSchema.parse(await response.json());
|
|
194888
|
+
},
|
|
194889
|
+
{
|
|
194890
|
+
retries: [250, 1e3],
|
|
194891
|
+
bail: (error49) => error49 instanceof Error && typeof error49.cause === "number" && error49.cause >= 400 && error49.cause < 500
|
|
194892
|
+
}
|
|
194893
|
+
);
|
|
194894
|
+
function subscriptionForModel(model) {
|
|
194895
|
+
if (!model.includes("/")) return null;
|
|
194896
|
+
const provider2 = getModelProvider(model);
|
|
194897
|
+
return provider2 === "anthropic" ? "CLAUDE_CODE_OAUTH_TOKEN" : provider2 === "openai" ? "CODEX_AUTH_JSON" : provider2 === "xai" ? "GROK_AUTH_JSON" : null;
|
|
194898
|
+
}
|
|
194899
|
+
async function initializeCredentialPool(input) {
|
|
194900
|
+
const access = input.ctx.credentialAccess;
|
|
194901
|
+
if (!access) return;
|
|
194902
|
+
const model = discoveryModel(input.slug);
|
|
194903
|
+
maskSecret(access.token);
|
|
194904
|
+
const names = /* @__PURE__ */ new Set([
|
|
194905
|
+
...access.candidates.map((candidate) => candidate.name),
|
|
194906
|
+
...subscriptionNameSchema.options,
|
|
194907
|
+
"ANTHROPIC_API_KEY",
|
|
194908
|
+
"OPENAI_API_KEY",
|
|
194909
|
+
"XAI_API_KEY"
|
|
194910
|
+
]);
|
|
194911
|
+
for (const name of names) {
|
|
194912
|
+
const value2 = process.env[name];
|
|
194913
|
+
if (value2 && !input.injected.includes(name)) workflowCredentials[name] = value2;
|
|
194914
|
+
}
|
|
194915
|
+
for (const name of subscriptionNameSchema.options) {
|
|
194916
|
+
if (model && subscriptionForModel(model) !== name) continue;
|
|
194917
|
+
if (workflowCredentials[name]) continue;
|
|
194918
|
+
const candidate = access.candidates.find((item) => item.name === name);
|
|
194919
|
+
if (!candidate) continue;
|
|
194920
|
+
const selected = await select({ access, candidate }).catch(() => {
|
|
194921
|
+
log.warning(`\xBB could not load ${name} for model discovery; selection will retry if needed`);
|
|
194922
|
+
return null;
|
|
194923
|
+
});
|
|
194924
|
+
if (!selected) continue;
|
|
194925
|
+
const value2 = sanitizeSecret(name, selected.value);
|
|
194926
|
+
if (!value2) continue;
|
|
194927
|
+
process.env[name] = value2;
|
|
194928
|
+
receipts[name] = selected.receipt;
|
|
194929
|
+
}
|
|
194930
|
+
saveReceipts();
|
|
194931
|
+
}
|
|
194932
|
+
function discoveryModel(slug2) {
|
|
194933
|
+
const value2 = process.env.PULLFROG_MODEL?.trim() || slug2?.trim();
|
|
194934
|
+
if (!value2) return void 0;
|
|
194935
|
+
return resolveCliModel(value2) ?? (value2.includes("/") ? value2 : void 0);
|
|
194936
|
+
}
|
|
194937
|
+
function resolvePoolModel(input) {
|
|
194938
|
+
const agent2 = resolveAgent({
|
|
194939
|
+
model: void 0,
|
|
194940
|
+
proxyModel: void 0,
|
|
194941
|
+
codexAgent: input.codexAgent
|
|
194942
|
+
});
|
|
194943
|
+
if (agent2.name === "opencode") return autoSelectModel();
|
|
194944
|
+
const provider2 = agent2.name === "claude" ? "anthropic" : "openai";
|
|
194945
|
+
return modelAliases.find(
|
|
194946
|
+
(alias) => alias.provider === provider2 && alias.preferred && !alias.hidden && !alias.fallback && !alias.routing
|
|
194947
|
+
)?.resolve;
|
|
194948
|
+
}
|
|
194949
|
+
async function selectConfiguredCredential(input) {
|
|
194950
|
+
const access = input.ctx.credentialAccess;
|
|
194951
|
+
const model = input.model;
|
|
194952
|
+
if (!access || !model?.includes("/") || getProviderGatewayUrl(model)) return false;
|
|
194953
|
+
const subscription = subscriptionForModel(model);
|
|
194954
|
+
const names = getModelEnvVars(model).filter((name) => name !== subscription);
|
|
194955
|
+
if (subscription) names.unshift(subscription);
|
|
194956
|
+
const candidates = access.candidates.filter(
|
|
194957
|
+
(candidate) => names.includes(candidate.name) && canInstallSubscription(candidate.name)
|
|
194958
|
+
);
|
|
194959
|
+
if (!candidates.length) return false;
|
|
194960
|
+
const refused = [];
|
|
194961
|
+
for (const name of names.filter(
|
|
194962
|
+
(item) => workflowCredentials[item] && canInstallSubscription(item)
|
|
194963
|
+
)) {
|
|
194964
|
+
const value2 = workflowCredentials[name];
|
|
194965
|
+
const verdict = await probe({ name, value: value2, model });
|
|
194966
|
+
if (usable(verdict)) {
|
|
194967
|
+
activate({ names, name, value: value2, receipt: void 0, model });
|
|
194968
|
+
return true;
|
|
194969
|
+
}
|
|
194970
|
+
refused.push(`${name} from the workflow: ${verdict.detail}`);
|
|
194971
|
+
log.info(`\xBB ${name} from the workflow unavailable; trying the next credential`);
|
|
194972
|
+
}
|
|
194973
|
+
for (const candidate of candidates) {
|
|
194974
|
+
const selected = await select({ access, candidate });
|
|
194975
|
+
if (!selected) continue;
|
|
194976
|
+
const verdict = await probe({ ...selected, model });
|
|
194977
|
+
await reportVerdict({ receipt: selected.receipt, verdict });
|
|
194978
|
+
if (usable(verdict)) {
|
|
194979
|
+
activate({ names, ...selected, model });
|
|
194980
|
+
log.info(`\xBB selected ${candidate.name} from ${candidate.source} scope`);
|
|
194981
|
+
return true;
|
|
194982
|
+
}
|
|
194983
|
+
refused.push(`${candidate.name} from ${candidate.source} scope: ${verdict.detail}`);
|
|
194984
|
+
log.info(
|
|
194985
|
+
`\xBB ${candidate.name} from ${candidate.source} scope unavailable; trying the next credential`
|
|
194986
|
+
);
|
|
194987
|
+
}
|
|
194988
|
+
throw new Error(
|
|
194989
|
+
`all configured credentials for ${model} were rejected or exhausted (${refused.join("; ")}); no other model or provider was selected`
|
|
194990
|
+
);
|
|
194991
|
+
}
|
|
194992
|
+
async function probe(input) {
|
|
194993
|
+
const subscription = subscriptionNameSchema.safeParse(input.name);
|
|
194994
|
+
const model = stripProviderPrefix(input.model);
|
|
194995
|
+
return subscription.success ? probeSubscription({ name: subscription.data, value: input.value, model }) : probeInference({ ...input, model });
|
|
194996
|
+
}
|
|
194997
|
+
function usable(verdict) {
|
|
194998
|
+
return verdict.status !== "rejected" && verdict.status !== "exhausted";
|
|
194999
|
+
}
|
|
195000
|
+
async function reportVerdict(input) {
|
|
195001
|
+
if (input.verdict.status === "unknown") return;
|
|
195002
|
+
await apiFetch({
|
|
195003
|
+
path: "/api/runtime/credentials/status",
|
|
195004
|
+
method: "POST",
|
|
195005
|
+
headers: { authorization: `Bearer ${input.receipt}`, "content-type": "application/json" },
|
|
195006
|
+
body: JSON.stringify(input.verdict),
|
|
195007
|
+
signal: AbortSignal.timeout(5e3)
|
|
195008
|
+
}).catch(() => log.debug("\xBB could not record a credential verdict"));
|
|
195009
|
+
}
|
|
195010
|
+
function activate(input) {
|
|
195011
|
+
for (const name of input.names) {
|
|
195012
|
+
delete process.env[name];
|
|
195013
|
+
delete receipts[name];
|
|
195014
|
+
if (name === "CODEX_AUTH_JSON" || name === "GROK_AUTH_JSON") clearInstalledSubscription(name);
|
|
195015
|
+
}
|
|
195016
|
+
const value2 = sanitizeSecret(input.name, input.value);
|
|
195017
|
+
if (!value2) throw new Error("configured credential was empty");
|
|
195018
|
+
process.env[input.name] = value2;
|
|
195019
|
+
if (input.receipt) receipts[input.name] = input.receipt;
|
|
195020
|
+
installCodexAuth();
|
|
195021
|
+
installXaiAuth();
|
|
195022
|
+
saveReceipts();
|
|
195023
|
+
authorizeModel(input.model);
|
|
194629
195024
|
}
|
|
194630
195025
|
|
|
194631
195026
|
// utils/gitAuthServer.ts
|
|
@@ -195331,75 +195726,6 @@ function buildModelAccessError(input) {
|
|
|
195331
195726
|
return [headline, "", why, "", cta].join("\n");
|
|
195332
195727
|
}
|
|
195333
195728
|
|
|
195334
|
-
// utils/normalizeEnv.ts
|
|
195335
|
-
function hasControlCharacter(value2) {
|
|
195336
|
-
return [...value2].some((ch) => {
|
|
195337
|
-
const code = ch.charCodeAt(0);
|
|
195338
|
-
return code < 32 || code === 127;
|
|
195339
|
-
});
|
|
195340
|
-
}
|
|
195341
|
-
function sanitizeSecret(key, value2) {
|
|
195342
|
-
const trimmed = value2.trim();
|
|
195343
|
-
if (trimmed.length === 0) {
|
|
195344
|
-
log.warning(
|
|
195345
|
-
`\xBB ${key} is whitespace-only \u2014 leaving env var unchanged. check your secret value.`
|
|
195346
|
-
);
|
|
195347
|
-
return null;
|
|
195348
|
-
}
|
|
195349
|
-
if (key.endsWith("_API_KEY") && hasControlCharacter(trimmed)) {
|
|
195350
|
-
log.warning(
|
|
195351
|
-
`\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.`
|
|
195352
|
-
);
|
|
195353
|
-
return null;
|
|
195354
|
-
}
|
|
195355
|
-
if (trimmed !== value2) {
|
|
195356
|
-
log.warning(
|
|
195357
|
-
`\xBB stripped whitespace from ${key} (whitespace in secret values breaks GitHub Actions log masking)`
|
|
195358
|
-
);
|
|
195359
|
-
}
|
|
195360
|
-
maskSecret(trimmed);
|
|
195361
|
-
return trimmed;
|
|
195362
|
-
}
|
|
195363
|
-
function normalizeEnv() {
|
|
195364
|
-
const upperKeys = /* @__PURE__ */ new Map();
|
|
195365
|
-
for (const key of Object.keys(process.env)) {
|
|
195366
|
-
const upper2 = key.toUpperCase();
|
|
195367
|
-
const existing = upperKeys.get(upper2) || [];
|
|
195368
|
-
existing.push(key);
|
|
195369
|
-
upperKeys.set(upper2, existing);
|
|
195370
|
-
}
|
|
195371
|
-
for (const [upperKey, keys] of upperKeys) {
|
|
195372
|
-
if (keys.length === 1) {
|
|
195373
|
-
const key = keys[0];
|
|
195374
|
-
if (key !== upperKey) {
|
|
195375
|
-
process.env[upperKey] = process.env[key];
|
|
195376
|
-
delete process.env[key];
|
|
195377
|
-
}
|
|
195378
|
-
continue;
|
|
195379
|
-
}
|
|
195380
|
-
const values = keys.map((k2) => process.env[k2]);
|
|
195381
|
-
const uniqueValues = new Set(values);
|
|
195382
|
-
if (uniqueValues.size > 1) {
|
|
195383
|
-
log.warning(
|
|
195384
|
-
`env var conflict: ${keys.join(", ")} have different values. using uppercase ${upperKey}.`
|
|
195385
|
-
);
|
|
195386
|
-
}
|
|
195387
|
-
const preferredKey = keys.find((k2) => k2 === upperKey) || keys[0];
|
|
195388
|
-
const preferredValue = process.env[preferredKey];
|
|
195389
|
-
for (const key of keys) {
|
|
195390
|
-
delete process.env[key];
|
|
195391
|
-
}
|
|
195392
|
-
process.env[upperKey] = preferredValue;
|
|
195393
|
-
}
|
|
195394
|
-
for (const key of Object.keys(process.env)) {
|
|
195395
|
-
if (!isSensitiveEnvName(key)) continue;
|
|
195396
|
-
const value2 = process.env[key];
|
|
195397
|
-
if (typeof value2 !== "string" || value2.length === 0) continue;
|
|
195398
|
-
const sanitized = sanitizeSecret(key, value2);
|
|
195399
|
-
if (sanitized !== null) process.env[key] = sanitized;
|
|
195400
|
-
}
|
|
195401
|
-
}
|
|
195402
|
-
|
|
195403
195729
|
// utils/overrides.ts
|
|
195404
195730
|
var DENIED_OVERRIDE_NAMES = /* @__PURE__ */ new Set([
|
|
195405
195731
|
"GITHUB_TOKEN",
|
|
@@ -195970,7 +196296,6 @@ ${ctx.error}` : ctx.error;
|
|
|
195970
196296
|
workflowRun: runId ? { owner: repoContext.owner, repo: repoContext.name, runId } : void 0,
|
|
195971
196297
|
customParts,
|
|
195972
196298
|
model: ctx.toolState.model,
|
|
195973
|
-
fallbackFrom: ctx.toolState.modelFallback?.from,
|
|
195974
196299
|
clamped: ctx.toolState.modelClamped,
|
|
195975
196300
|
unselectedProxyDefault: ctx.toolState.unselectedProxyDefault,
|
|
195976
196301
|
oss: ctx.toolState.oss,
|
|
@@ -196354,7 +196679,8 @@ async function fetchRunContext(params) {
|
|
|
196354
196679
|
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
196355
196680
|
try {
|
|
196356
196681
|
const headers = {
|
|
196357
|
-
Authorization: `Bearer ${params.token}
|
|
196682
|
+
Authorization: `Bearer ${params.token}`,
|
|
196683
|
+
"X-Pullfrog-Credential-Pools": "1"
|
|
196358
196684
|
};
|
|
196359
196685
|
if (params.oidcToken) {
|
|
196360
196686
|
headers["X-GitHub-OIDC-Token"] = params.oidcToken;
|
|
@@ -197651,16 +197977,20 @@ async function main() {
|
|
|
197651
197977
|
const preIntrospectionDirty = repoDir ? await dirtyTrackedPaths({ cwd: repoDir }) : null;
|
|
197652
197978
|
const opencodeCliPath = await agents.opencode.install();
|
|
197653
197979
|
captureBaselineModels(opencodeCliPath);
|
|
197980
|
+
const injected = [];
|
|
197654
197981
|
if (runContext.dbSecrets) {
|
|
197655
197982
|
for (const [key, value2] of Object.entries(runContext.dbSecrets)) {
|
|
197656
197983
|
if (!process.env[key]) {
|
|
197657
197984
|
const sanitized = sanitizeSecret(key, value2);
|
|
197658
|
-
if (sanitized
|
|
197985
|
+
if (sanitized === null) continue;
|
|
197986
|
+
process.env[key] = sanitized;
|
|
197987
|
+
injected.push(key);
|
|
197659
197988
|
}
|
|
197660
197989
|
}
|
|
197661
197990
|
const count = Object.keys(runContext.dbSecrets).length;
|
|
197662
197991
|
if (count > 0) log.info(`\xBB ${count} db secret(s) loaded`);
|
|
197663
197992
|
}
|
|
197993
|
+
await initializeCredentialPool({ ctx: runContext, slug: payload.model, injected });
|
|
197664
197994
|
installCodexAuth();
|
|
197665
197995
|
installXaiAuth();
|
|
197666
197996
|
captureAuthorizedModels(opencodeCliPath);
|
|
@@ -197734,13 +198064,23 @@ async function main() {
|
|
|
197734
198064
|
}
|
|
197735
198065
|
const gitAuthServer = __using(_stack2, await startGitAuthServer(tmpdir3), true);
|
|
197736
198066
|
setGitAuthServer(gitAuthServer);
|
|
198067
|
+
let resolvedModel = resolveModel({ slug: payload.model });
|
|
198068
|
+
if (!resolvedModel && !payload.proxyModel && runContext.credentialAccess?.candidates.length) {
|
|
198069
|
+
resolvedModel = resolvePoolModel({
|
|
198070
|
+
codexAgent: runContext.repoSettings.codexAgent || payload.codexArm === true
|
|
198071
|
+
});
|
|
198072
|
+
}
|
|
198073
|
+
let poolSelected = !payload.proxyModel && await selectConfiguredCredential({
|
|
198074
|
+
ctx: runContext,
|
|
198075
|
+
model: resolvedModel
|
|
198076
|
+
});
|
|
197737
198077
|
const access = decideModelAccess({
|
|
197738
198078
|
modelExplicit: payload.modelExplicit ?? false,
|
|
197739
198079
|
model: payload.model,
|
|
197740
198080
|
oss: runContext.oss,
|
|
197741
198081
|
proxyActive: !!payload.proxyModel,
|
|
197742
198082
|
subsidyTarget: runContext.proxyModel,
|
|
197743
|
-
resolvedModel
|
|
198083
|
+
resolvedModel,
|
|
197744
198084
|
authorized: getAuthorizedModels()
|
|
197745
198085
|
});
|
|
197746
198086
|
if (access.kind === "error") {
|
|
@@ -197754,15 +198094,19 @@ async function main() {
|
|
|
197754
198094
|
);
|
|
197755
198095
|
}
|
|
197756
198096
|
if (access.kind === "proxy") payload.proxyModel = access.target;
|
|
197757
|
-
if (access.kind === "byok")
|
|
198097
|
+
if (access.kind === "byok") {
|
|
198098
|
+
payload.proxyModel = void 0;
|
|
198099
|
+
if (!poolSelected)
|
|
198100
|
+
poolSelected = await selectConfiguredCredential({
|
|
198101
|
+
ctx: runContext,
|
|
198102
|
+
model: resolvedModel
|
|
198103
|
+
});
|
|
198104
|
+
}
|
|
197758
198105
|
if (runContext.oss && payload.proxyModel) {
|
|
197759
198106
|
payload.effort = ossEffortFloor({ payload });
|
|
197760
198107
|
}
|
|
197761
|
-
|
|
197762
|
-
const credentials = payload.proxyModel ? { kind: "ok" } : await checkConfiguredCredentials({
|
|
197763
|
-
model: configuredModel,
|
|
197764
|
-
authorized: getAuthorizedModels()
|
|
197765
|
-
});
|
|
198108
|
+
if (payload.proxyModel) resolvedModel = void 0;
|
|
198109
|
+
const credentials = payload.proxyModel || poolSelected ? { kind: "ok" } : await checkConfiguredCredentials({ model: resolvedModel });
|
|
197766
198110
|
if (credentials.kind === "dead") {
|
|
197767
198111
|
throw new Error(
|
|
197768
198112
|
buildRejectedCredentialError({
|
|
@@ -197774,13 +198118,6 @@ async function main() {
|
|
|
197774
198118
|
})
|
|
197775
198119
|
);
|
|
197776
198120
|
}
|
|
197777
|
-
if (credentials.kind === "fellBack" && configuredModel) {
|
|
197778
|
-
log.warning(
|
|
197779
|
-
`\xBB ${credentials.credential} was rejected by its provider \u2014 running ${credentials.replacement} instead of ${configuredModel}`
|
|
197780
|
-
);
|
|
197781
|
-
toolState.modelFallback = { from: configuredModel };
|
|
197782
|
-
}
|
|
197783
|
-
const resolvedModel = credentials.kind === "fellBack" ? credentials.replacement : configuredModel;
|
|
197784
198121
|
vertexCredentials = materializeVertexCredentials({ model: resolvedModel });
|
|
197785
198122
|
let agent2 = resolveAgent({
|
|
197786
198123
|
model: resolvedModel,
|
|
@@ -198090,12 +198427,12 @@ ${instructions.user}` : null,
|
|
|
198090
198427
|
if (payload.timeout === TIMEOUT_DISABLED) {
|
|
198091
198428
|
result = await Promise.race([agentPromise, activityTimeout.promise]);
|
|
198092
198429
|
} else {
|
|
198093
|
-
const
|
|
198094
|
-
if (payload.timeout &&
|
|
198430
|
+
const usable2 = resolveTimeoutMs(payload.timeout);
|
|
198431
|
+
if (payload.timeout && usable2 === null) {
|
|
198095
198432
|
log.warning(`invalid timeout "${payload.timeout}" (use --notimeout to disable), using 1h`);
|
|
198096
198433
|
}
|
|
198097
|
-
const timeoutMs =
|
|
198098
|
-
const actualTimeout =
|
|
198434
|
+
const timeoutMs = usable2 ?? 36e5;
|
|
198435
|
+
const actualTimeout = usable2 !== null ? payload.timeout : "1h";
|
|
198099
198436
|
let timeoutId;
|
|
198100
198437
|
const timeoutPromise = new Promise((_4, reject) => {
|
|
198101
198438
|
timeoutId = setTimeout(() => {
|