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/internal/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { CLI_CONTRACT_HEADER, CLI_CONTRACT_VERSION, CLI_UPGRADE_MESSAGE } from "
|
|
|
6
6
|
export type { ConfigField, ConfigScope, ConfigValue } from "../configuration.ts";
|
|
7
7
|
export { configEntrySchema, configFields, configResponseSchema, configValueSchema, } from "../configuration.ts";
|
|
8
8
|
export type { AuthorPermission, AutoTier, EffortPosition, ModelAlias, ModelProvider, Payload, PayloadEvent, PayloadRouting, ProviderConfig, PushPermission, RouterSource, RouterTier, ShellPermission, ToolPermission, WriteablePayload, XrepoConfig, } from "../external.ts";
|
|
9
|
-
export { AUTO_EFFICIENT, AUTO_INTELLIGENT, AUTO_ROUTER, autoLadder, autoRoutedSlug, DEFAULT_EFFORT_POSITION, DEFAULT_PROXY_MODEL, defaultAutoTier, EFFORT_ALIASES, getAutoSelectHintModel, getModelEffortLevels, getModelEnvVars, getModelManagedCredentials, getModelProvider, getProviderDisplayName, isAutoRouted, isAutoSlug, isAutoTier, isCardGatedModel, isEffortPosition, isOssAllowedModel, isRouterTier, ladderRungs, modelAliases, modelHasStoredAuth, OSS_MODEL_ALLOWLIST, OSS_RECOMMENDED_MODEL, offeredRungs, ossFundedModelNames, PROVIDER_GATEWAY_URL_ENV, parseEffortPosition, parseModel, providers, pullfrogMcpName, ROUTED_PROVIDERS, ROUTER_LADDER, ROUTER_TIERS, resolveAutoTier, resolveCliModel, resolveDisplayAlias, resolveModelRung, resolveModelSlug, resolveOpenRouterModel, resolveRoutedModel, resolveRung, rungLabel, rungPosition, SUBSIDY_RUNG, } from "../external.ts";
|
|
9
|
+
export { AUTO_EFFICIENT, AUTO_INTELLIGENT, AUTO_ROUTER, autoLadder, autoRoutedSlug, DEFAULT_EFFORT_POSITION, DEFAULT_PROXY_MODEL, defaultAutoTier, EFFORT_ALIASES, getAutoSelectHintModel, getModelEffortLevels, getModelEnvVars, getModelManagedCredentials, getModelProvider, getProviderDisplayName, isAutoRouted, isAutoSlug, isAutoTier, isCardGatedModel, isEffortPosition, isOssAllowedModel, isRouterTier, ladderRungs, modelAliases, modelHasStoredAuth, modelIdLabel, OSS_MODEL_ALLOWLIST, OSS_RECOMMENDED_MODEL, offeredRungs, ossFundedModelNames, PROVIDER_GATEWAY_URL_ENV, parseEffortPosition, parseModel, pickUsesOpenRouter, providers, pullfrogMcpName, ROUTED_PROVIDERS, ROUTER_LADDER, ROUTER_TIERS, resolveAutoTier, resolveCliModel, resolveDisplayAlias, resolveModelRung, resolveModelSlug, resolveOpenRouterModel, resolveRoutedModel, resolveRung, routerProxyOptOut, rungLabel, rungPosition, SUBSIDY_RUNG, } from "../external.ts";
|
|
10
10
|
export type { Mode } from "../modes.ts";
|
|
11
11
|
export { modes } from "../modes.ts";
|
|
12
12
|
export { type CommercialRefusal, commercialPaywallBody } from "../utils/billingErrors.ts";
|
package/dist/internal.js
CHANGED
|
@@ -8,7 +8,7 @@ var __export = (target, all) => {
|
|
|
8
8
|
// package.json
|
|
9
9
|
var package_default = {
|
|
10
10
|
name: "pullfrog",
|
|
11
|
-
version: "0.1.
|
|
11
|
+
version: "0.1.92",
|
|
12
12
|
cliContractVersion: "1",
|
|
13
13
|
type: "module",
|
|
14
14
|
bin: {
|
|
@@ -15571,6 +15571,9 @@ function getProviderDisplayName(slug) {
|
|
|
15571
15571
|
const parsed = parseModel(slug);
|
|
15572
15572
|
return providers[parsed.provider]?.displayName;
|
|
15573
15573
|
}
|
|
15574
|
+
function modelIdLabel(specifier) {
|
|
15575
|
+
return specifier.slice(specifier.lastIndexOf("/") + 1).replace(/^~/, "");
|
|
15576
|
+
}
|
|
15574
15577
|
function getModelEnvVars(slug) {
|
|
15575
15578
|
const parsed = parseModel(slug);
|
|
15576
15579
|
const providerConfig = providers[parsed.provider];
|
|
@@ -15601,6 +15604,13 @@ function modelHasStoredAuth(params) {
|
|
|
15601
15604
|
];
|
|
15602
15605
|
return authVars.some((v) => params.secretNames.includes(v));
|
|
15603
15606
|
}
|
|
15607
|
+
function routerProxyOptOut(ctx) {
|
|
15608
|
+
if (ctx.slug === null) return false;
|
|
15609
|
+
return resolveDisplayAlias(ctx.slug)?.isFree === true || modelHasStoredAuth({ model: ctx.slug, secretNames: ctx.secretNames });
|
|
15610
|
+
}
|
|
15611
|
+
function pickUsesOpenRouter(params) {
|
|
15612
|
+
return params.proxied && !routerProxyOptOut(params);
|
|
15613
|
+
}
|
|
15604
15614
|
var modelAliases = Object.entries(providers).flatMap(
|
|
15605
15615
|
([providerKey, config2]) => Object.entries(config2.models).map(([modelId, def]) => ({
|
|
15606
15616
|
slug: `${providerKey}/${modelId}`,
|
|
@@ -16246,32 +16256,20 @@ function commercialPaywallBody(params) {
|
|
|
16246
16256
|
// utils/buildPullfrogFooter.ts
|
|
16247
16257
|
var PULLFROG_DIVIDER = "<!-- PULLFROG_DIVIDER_DO_NOT_REMOVE_PLZ -->";
|
|
16248
16258
|
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>`;
|
|
16249
|
-
function providerDisplayName(slug) {
|
|
16250
|
-
try {
|
|
16251
|
-
const key = getModelProvider(slug);
|
|
16252
|
-
const meta3 = providers[key];
|
|
16253
|
-
return meta3?.displayName ?? key;
|
|
16254
|
-
} catch {
|
|
16255
|
-
return slug;
|
|
16256
|
-
}
|
|
16257
|
-
}
|
|
16258
16259
|
function formatModelLabel(params) {
|
|
16259
|
-
const
|
|
16260
|
+
const slugAlias = resolveDisplayAlias(params.model);
|
|
16261
|
+
const alias = slugAlias ?? // reverse-lookup: when the caller passes an effective model (proxy or
|
|
16260
16262
|
// resolved target like "openrouter/anthropic/claude-opus-4.7") instead of
|
|
16261
|
-
// a stored alias slug, find the alias whose resolve target matches
|
|
16262
|
-
// still render a friendly display name.
|
|
16263
|
+
// a stored alias slug, find the alias whose resolve target matches.
|
|
16263
16264
|
modelAliases.find((a) => a.resolve === params.model || a.openRouterResolve === params.model);
|
|
16264
|
-
const
|
|
16265
|
+
const modelName = slugAlias?.routing ? slugAlias.displayName : modelIdLabel(slugAlias?.resolve ?? params.model);
|
|
16265
16266
|
if (params.oss) {
|
|
16266
|
-
const ossBase = `\`${
|
|
16267
|
+
const ossBase = `\`${modelName}\` (free via [Pullfrog for OSS](https://pullfrog.com/for-oss))`;
|
|
16267
16268
|
if (params.clamped?.reason !== "oss") return ossBase;
|
|
16268
16269
|
const configured = isAutoTier(params.clamped.from) ? "the intelligent tier" : `\`${resolveDisplayAlias(params.clamped.from)?.displayName ?? params.clamped.from}\``;
|
|
16269
16270
|
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)`;
|
|
16270
16271
|
}
|
|
16271
|
-
const base = alias?.isFree ? `\`${
|
|
16272
|
-
if (params.fallbackFrom) {
|
|
16273
|
-
return `${base} (credentials for ${providerDisplayName(params.fallbackFrom)} were rejected by the provider)`;
|
|
16274
|
-
}
|
|
16272
|
+
const base = alias?.isFree ? `\`${modelName}\` (free)` : `\`${modelName}\``;
|
|
16275
16273
|
if (params.clamped?.reason === "trial") {
|
|
16276
16274
|
return `${base} (model usage covered by Pullfrog)`;
|
|
16277
16275
|
}
|
|
@@ -16312,7 +16310,6 @@ function buildPullfrogFooter(params) {
|
|
|
16312
16310
|
parts.push(
|
|
16313
16311
|
`Using ${formatModelLabel({
|
|
16314
16312
|
model: params.model,
|
|
16315
|
-
fallbackFrom: params.fallbackFrom,
|
|
16316
16313
|
clamped: params.clamped,
|
|
16317
16314
|
unselectedProxyDefault: params.unselectedProxyDefault,
|
|
16318
16315
|
oss: params.oss
|
|
@@ -16480,10 +16477,12 @@ async function preflightClaudeSubscription(params) {
|
|
|
16480
16477
|
if (res.status !== 401 && res.status !== 403 && res.status !== 429)
|
|
16481
16478
|
return { usable: true, status: res.status };
|
|
16482
16479
|
const body = await res.text().catch(() => "");
|
|
16480
|
+
const reset = Number(res.headers.get("anthropic-ratelimit-unified-reset"));
|
|
16483
16481
|
return {
|
|
16484
16482
|
usable: false,
|
|
16485
16483
|
status: res.status,
|
|
16486
|
-
reason: `${res.status}: ${extractApiErrorMessage(body)}
|
|
16484
|
+
reason: `${res.status}: ${extractApiErrorMessage(body)}`,
|
|
16485
|
+
resetAt: res.status === 429 && reset > 0 ? new Date(reset * 1e3) : void 0
|
|
16487
16486
|
};
|
|
16488
16487
|
}
|
|
16489
16488
|
function extractApiErrorMessage(body) {
|
|
@@ -17090,7 +17089,10 @@ var subscriptionSchema = external_exports.object({
|
|
|
17090
17089
|
owner: external_exports.string(),
|
|
17091
17090
|
inherited: external_exports.boolean(),
|
|
17092
17091
|
shared: external_exports.boolean(),
|
|
17093
|
-
registeredAt: external_exports.string()
|
|
17092
|
+
registeredAt: external_exports.string(),
|
|
17093
|
+
/** the provider's last verdict on the current value; null = usable or never checked. */
|
|
17094
|
+
status: external_exports.enum(["rejected", "exhausted"]).nullish(),
|
|
17095
|
+
resetAt: external_exports.string().nullish()
|
|
17094
17096
|
});
|
|
17095
17097
|
var subscriptionListSchema = external_exports.object({
|
|
17096
17098
|
subscriptions: external_exports.array(subscriptionSchema),
|
|
@@ -17190,9 +17192,16 @@ var identitySchema = external_exports.object({
|
|
|
17190
17192
|
account_id: external_exports.string().optional(),
|
|
17191
17193
|
email_verified: external_exports.boolean().optional()
|
|
17192
17194
|
});
|
|
17195
|
+
var codexWindowSchema = external_exports.object({ used_percent: external_exports.number().optional(), reset_at: external_exports.number().optional() }).nullish();
|
|
17193
17196
|
var codexUsageSchema = external_exports.object({
|
|
17194
|
-
rate_limit: external_exports.object({
|
|
17197
|
+
rate_limit: external_exports.object({
|
|
17198
|
+
allowed: external_exports.boolean(),
|
|
17199
|
+
limit_reached: external_exports.boolean(),
|
|
17200
|
+
primary_window: codexWindowSchema,
|
|
17201
|
+
secondary_window: codexWindowSchema
|
|
17202
|
+
}).optional()
|
|
17195
17203
|
});
|
|
17204
|
+
var unknown2 = { status: "unknown" };
|
|
17196
17205
|
var SubscriptionCredentialError = class extends Error {
|
|
17197
17206
|
};
|
|
17198
17207
|
async function subscriptionIdentity(input) {
|
|
@@ -17231,12 +17240,16 @@ async function readSubscriptionEndpoint(input) {
|
|
|
17231
17240
|
async function probeSubscription(input) {
|
|
17232
17241
|
if (input.name === "CLAUDE_CODE_OAUTH_TOKEN") {
|
|
17233
17242
|
const result = await preflightClaudeSubscription({ token: input.value, model: input.model });
|
|
17234
|
-
|
|
17243
|
+
if (result.usable)
|
|
17244
|
+
return result.status !== void 0 && result.status < 300 ? { status: "usable" } : unknown2;
|
|
17245
|
+
return result.status === 429 ? { status: "exhausted", detail: result.reason, resetAt: result.resetAt } : { status: "rejected", detail: result.reason };
|
|
17235
17246
|
}
|
|
17236
17247
|
const codex = input.name === "CODEX_AUTH_JSON" ? parseCodexAuthBody(input.value) : null;
|
|
17237
17248
|
const grok = input.name === "GROK_AUTH_JSON" ? parseXaiAuthBody(input.value) : null;
|
|
17238
17249
|
const auth = codex ?? grok;
|
|
17239
|
-
if (!auth
|
|
17250
|
+
if (!auth) return { status: "rejected", detail: "the stored credential is malformed" };
|
|
17251
|
+
if (auth.refresh_rejected_at)
|
|
17252
|
+
return { status: "rejected", detail: `refresh rejected at ${auth.refresh_rejected_at}` };
|
|
17240
17253
|
if (grok && input.model)
|
|
17241
17254
|
return probeInference({
|
|
17242
17255
|
name: "XAI_API_KEY",
|
|
@@ -17249,23 +17262,31 @@ async function probeSubscription(input) {
|
|
|
17249
17262
|
accountId: codex?.tokens.account_id,
|
|
17250
17263
|
grokBilling: !!grok
|
|
17251
17264
|
});
|
|
17252
|
-
if (response?.status === 401) return "rejected";
|
|
17253
|
-
if (!response?.ok) return
|
|
17265
|
+
if (response?.status === 401) return { status: "rejected", detail: "rejected with 401" };
|
|
17266
|
+
if (!response?.ok) return unknown2;
|
|
17254
17267
|
const body = await response.json().catch(() => null);
|
|
17255
|
-
if (codex)
|
|
17256
|
-
|
|
17257
|
-
|
|
17258
|
-
|
|
17259
|
-
|
|
17260
|
-
|
|
17261
|
-
|
|
17268
|
+
if (!codex) return unknown2;
|
|
17269
|
+
const parsed = codexUsageSchema.safeParse(body);
|
|
17270
|
+
const limit = parsed.success ? parsed.data.rate_limit : void 0;
|
|
17271
|
+
if (!limit) return unknown2;
|
|
17272
|
+
if (limit.allowed && !limit.limit_reached) return { status: "usable" };
|
|
17273
|
+
const windows = [limit.primary_window, limit.secondary_window];
|
|
17274
|
+
const full = windows.filter((window) => (window?.used_percent ?? 0) >= 100);
|
|
17275
|
+
const resets = (full.length ? full : windows).flatMap(
|
|
17276
|
+
(window) => window?.reset_at ? [window.reset_at] : []
|
|
17277
|
+
);
|
|
17278
|
+
return {
|
|
17279
|
+
status: "exhausted",
|
|
17280
|
+
detail: "the ChatGPT plan's usage limit is reached",
|
|
17281
|
+
resetAt: resets.length ? new Date(Math.max(...resets) * 1e3) : void 0
|
|
17282
|
+
};
|
|
17262
17283
|
}
|
|
17263
17284
|
async function probeInference(input) {
|
|
17264
17285
|
const anthropic = input.name === "ANTHROPIC_API_KEY";
|
|
17265
17286
|
const openai = input.name === "OPENAI_API_KEY";
|
|
17266
17287
|
if (!anthropic && !openai && input.name !== "XAI_API_KEY") {
|
|
17267
17288
|
const result = await verifyCredential({ envVar: input.name, value: input.value });
|
|
17268
|
-
return result === "dead" ? "rejected" : result === "alive" ? "usable" :
|
|
17289
|
+
return result === "dead" ? { status: "rejected", detail: "rejected by its provider" } : result === "alive" ? { status: "usable" } : unknown2;
|
|
17269
17290
|
}
|
|
17270
17291
|
const headers = new Headers({ "content-type": "application/json" });
|
|
17271
17292
|
if (anthropic) {
|
|
@@ -17290,12 +17311,20 @@ async function probeInference(input) {
|
|
|
17290
17311
|
).catch(() => null);
|
|
17291
17312
|
if (response?.status === 400 && input.name === "XAI_API_KEY") {
|
|
17292
17313
|
const error48 = external_exports.object({ error: external_exports.string() }).safeParse(await response.json().catch(() => null));
|
|
17293
|
-
return error48.success && error48.data.error.startsWith("Incorrect API key provided.") ? "rejected" : "
|
|
17314
|
+
return error48.success && error48.data.error.startsWith("Incorrect API key provided.") ? { status: "rejected", detail: "rejected with 400: incorrect API key" } : unknown2;
|
|
17315
|
+
}
|
|
17316
|
+
if (response?.status === 429 && openai) {
|
|
17317
|
+
const error48 = external_exports.object({ error: external_exports.object({ code: external_exports.string() }) }).safeParse(await response.json().catch(() => null));
|
|
17318
|
+
return error48.success && error48.data.error.code === "insufficient_quota" ? { status: "exhausted", detail: "refused with 429: insufficient quota", resetAt: void 0 } : unknown2;
|
|
17294
17319
|
}
|
|
17295
17320
|
await response?.body?.cancel();
|
|
17296
|
-
if (!response) return
|
|
17297
|
-
if (response.ok) return "usable";
|
|
17298
|
-
|
|
17321
|
+
if (!response) return unknown2;
|
|
17322
|
+
if (response.ok) return { status: "usable" };
|
|
17323
|
+
if (response.status === 401 || response.status === 403)
|
|
17324
|
+
return { status: "rejected", detail: `rejected with ${response.status}` };
|
|
17325
|
+
if (response.status === 402)
|
|
17326
|
+
return { status: "exhausted", detail: "refused with 402", resetAt: void 0 };
|
|
17327
|
+
return unknown2;
|
|
17299
17328
|
}
|
|
17300
17329
|
|
|
17301
17330
|
// utils/time.ts
|
|
@@ -17377,6 +17406,7 @@ export {
|
|
|
17377
17406
|
ladderRungs,
|
|
17378
17407
|
modelAliases,
|
|
17379
17408
|
modelHasStoredAuth,
|
|
17409
|
+
modelIdLabel,
|
|
17380
17410
|
modes,
|
|
17381
17411
|
offeredRungs,
|
|
17382
17412
|
ossFundedModelNames,
|
|
@@ -17385,6 +17415,7 @@ export {
|
|
|
17385
17415
|
parseModel,
|
|
17386
17416
|
parseTimeString,
|
|
17387
17417
|
parseXaiAuthBody,
|
|
17418
|
+
pickUsesOpenRouter,
|
|
17388
17419
|
probeSubscription,
|
|
17389
17420
|
providers,
|
|
17390
17421
|
pullfrogMcpName,
|
|
@@ -17398,6 +17429,7 @@ export {
|
|
|
17398
17429
|
resolveOpenRouterModel,
|
|
17399
17430
|
resolveRoutedModel,
|
|
17400
17431
|
resolveRung,
|
|
17432
|
+
routerProxyOptOut,
|
|
17401
17433
|
runStatusCheckNeedsFinalizing,
|
|
17402
17434
|
rungLabel,
|
|
17403
17435
|
rungPosition,
|
package/dist/models.d.ts
CHANGED
|
@@ -140,6 +140,9 @@ export declare function getModelProvider(slug: string): string;
|
|
|
140
140
|
export declare function getProviderDisplayName(slug: string): string | undefined;
|
|
141
141
|
/** provider-prefixed specifier → bare model id (`anthropic/claude-opus-5` → `claude-opus-5`). */
|
|
142
142
|
export declare function stripProviderPrefix(specifier: string): string;
|
|
143
|
+
/** specifier → the model's own id, for display: `openrouter/~anthropic/claude-opus-latest` →
|
|
144
|
+
* `claude-opus-latest`. names what an alias points at today, which its name does not. */
|
|
145
|
+
export declare function modelIdLabel(specifier: string): string;
|
|
143
146
|
export declare function getModelEnvVars(slug: string): string[];
|
|
144
147
|
/** managed credentials are authored only via `pullfrog auth <provider>` — they
|
|
145
148
|
* count as "configured" for hasAnyKey-style UI checks but are never offered as
|
|
@@ -180,6 +183,28 @@ export declare function modelHasStoredAuth(params: {
|
|
|
180
183
|
model: string;
|
|
181
184
|
secretNames: string[];
|
|
182
185
|
}): boolean;
|
|
186
|
+
/**
|
|
187
|
+
* `proxyOptOut`, spelled the way run-context spells it: a Router run skips the
|
|
188
|
+
* mint when the pick is FREE or a Pullfrog-stored key covers it, and then runs
|
|
189
|
+
* as-configured rather than clamping to the auto tier.
|
|
190
|
+
*
|
|
191
|
+
* one predicate for every reader — the console picker's `customLocked`, the
|
|
192
|
+
* effort ladder's `resolveRunning` and `pickUsesOpenRouter`. they held two
|
|
193
|
+
* copies and the free half was missing from both, which put the whole `opencode/*` free tier (`opencode/big-pickle`
|
|
194
|
+
* is `preferred`, so the most-picked model there is) behind "add a payment
|
|
195
|
+
* method to run it" on a card-less Router repo, against a server that ran it.
|
|
196
|
+
*/
|
|
197
|
+
export declare function routerProxyOptOut(ctx: {
|
|
198
|
+
slug: string | null;
|
|
199
|
+
secretNames: string[];
|
|
200
|
+
}): boolean;
|
|
201
|
+
/** whether a pick runs as its OpenRouter target: a proxied account (Router, the OSS subsidy)
|
|
202
|
+
* mints for every pick `routerProxyOptOut` does not exempt. */
|
|
203
|
+
export declare function pickUsesOpenRouter(params: {
|
|
204
|
+
slug: string;
|
|
205
|
+
proxied: boolean;
|
|
206
|
+
secretNames: string[];
|
|
207
|
+
}): boolean;
|
|
183
208
|
export declare const modelAliases: ModelAlias[];
|
|
184
209
|
/**
|
|
185
210
|
* `repo.model` sentinels for the two managed auto tiers. stored verbatim in
|
package/dist/toolState.d.ts
CHANGED
|
@@ -138,9 +138,6 @@ export interface ToolState {
|
|
|
138
138
|
model?: string | undefined;
|
|
139
139
|
agent?: string | undefined;
|
|
140
140
|
credential?: AgentCredential | undefined;
|
|
141
|
-
modelFallback?: {
|
|
142
|
-
from: string;
|
|
143
|
-
} | undefined;
|
|
144
141
|
unselectedProxyDefault?: boolean | undefined;
|
|
145
142
|
modelClamped?: {
|
|
146
143
|
from: string;
|
|
@@ -15,21 +15,14 @@ export interface BuildPullfrogFooterParams {
|
|
|
15
15
|
workflowRunUrl?: string | undefined;
|
|
16
16
|
/** arbitrary custom parts (e.g., action links) */
|
|
17
17
|
customParts?: string[] | undefined;
|
|
18
|
-
/** model slug from payload (e.g., "anthropic/claude-opus"). shown in footer as "Using `
|
|
18
|
+
/** model slug from payload (e.g., "anthropic/claude-opus"). shown in footer as the model it runs: "Using `claude-opus-5-5`" */
|
|
19
19
|
model?: string | undefined;
|
|
20
|
-
/**
|
|
21
|
-
* When a credential was rejected and the run moved to another model, this is
|
|
22
|
-
* the slug the user had configured (e.g. "anthropic/claude-opus") — the footer
|
|
23
|
-
* renders `Using <model> (credentials for <configured> were rejected by the
|
|
24
|
-
* provider)` so the substitution is visible in PR comments + reviews.
|
|
25
|
-
*/
|
|
26
|
-
fallbackFrom?: string | undefined;
|
|
27
20
|
/**
|
|
28
21
|
* When a Router account had a model (or the intelligent tier) selected that
|
|
29
22
|
* the server clamped to the efficient default — custom picks are card-gated
|
|
30
23
|
* wholesale. `from` is the configured slug (e.g. "anthropic/claude-opus");
|
|
31
24
|
* `reason` names the binding constraint — "card" (no card on file) renders
|
|
32
|
-
* `Using <
|
|
25
|
+
* `Using <kimi-k2> (<Claude Opus> needs a card on file)`, "noRouterPath"
|
|
33
26
|
* (no openRouterResolve yet and no stored provider key) renders a
|
|
34
27
|
* provider-key nudge — so the downgrade is visible rather than silently
|
|
35
28
|
* presenting Kimi as the pick.
|
|
@@ -26,7 +26,8 @@ export interface InstalledCodexAuth {
|
|
|
26
26
|
originalIdToken: string | undefined;
|
|
27
27
|
}
|
|
28
28
|
/** materialize CODEX_AUTH_JSON from env into a disk path OpenCode reads from.
|
|
29
|
-
* returns null when the env var is absent, malformed, or wrong auth mode
|
|
29
|
+
* returns null when the env var is absent, malformed, or wrong auth mode, or
|
|
30
|
+
* when this runner cannot host it (`resolveDataHome`) —
|
|
30
31
|
* caller treats null as "no codex auth, fall through to API key flow".
|
|
31
32
|
*
|
|
32
33
|
* The env value is server-side guaranteed fresh by `maybeRotateCodexSecret`
|
|
@@ -52,7 +53,8 @@ export interface InstalledXaiAuth {
|
|
|
52
53
|
* The plugin sends the token to `api.x.ai/v1` — it deliberately sets no
|
|
53
54
|
* baseURL — so there is no CLI proxy and no client-version header in play.
|
|
54
55
|
*
|
|
55
|
-
* returns null when the env var is absent or malformed
|
|
56
|
+
* returns null when the env var is absent or malformed, or when this runner
|
|
57
|
+
* cannot host it (`resolveDataHome`); caller treats null as
|
|
56
58
|
* "no grok subscription auth, fall through to XAI_API_KEY". */
|
|
57
59
|
export declare function installXaiAuth(): InstalledXaiAuth | null;
|
|
58
60
|
export interface InstalledCodexHome {
|
|
@@ -76,3 +78,5 @@ export interface InstalledCodexHome {
|
|
|
76
78
|
* call. callers treat null as "codex CLI can't use this credential".
|
|
77
79
|
*/
|
|
78
80
|
export declare function installCodexHome(): InstalledCodexHome | null;
|
|
81
|
+
/** false for a Codex or Grok subscription on a runner that cannot host it (`resolveDataHome`). */
|
|
82
|
+
export declare function canInstallSubscription(name: string): boolean;
|
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `ok` covers "the credential works", "we couldn't tell", and "something else
|
|
3
3
|
* on this model still works" — the run proceeds as it did before this check
|
|
4
|
-
* existed.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* deciding an alternative exists and picking it have to be the SAME decision,
|
|
9
|
-
* or the picker can land back on the provider that just failed.
|
|
4
|
+
* existed. `dead` is only reached when every credential the configured model
|
|
5
|
+
* could use has been explicitly rejected by its own provider. A run never moves
|
|
6
|
+
* to a different model: the pool of credentials for THIS provider is where a
|
|
7
|
+
* rejected one is routed around (see wiki/subscription-credentials.md).
|
|
10
8
|
*/
|
|
11
9
|
export type CredentialOutcome = {
|
|
12
10
|
kind: "ok";
|
|
13
|
-
} | {
|
|
14
|
-
kind: "fellBack";
|
|
15
|
-
credential: string;
|
|
16
|
-
reason: string | undefined;
|
|
17
|
-
replacement: string;
|
|
18
11
|
} | {
|
|
19
12
|
kind: "dead";
|
|
20
13
|
credential: string;
|
|
@@ -22,10 +15,10 @@ export type CredentialOutcome = {
|
|
|
22
15
|
};
|
|
23
16
|
/**
|
|
24
17
|
* Ask the providers whether the credentials behind the configured model still
|
|
25
|
-
* work, BEFORE the agent spawns — so a rejected one becomes either a run on
|
|
26
|
-
*
|
|
27
|
-
* seconds in against a "rotate your GitHub Actions secret"
|
|
28
|
-
* the user never put a key.
|
|
18
|
+
* work, BEFORE the agent spawns — so a rejected one becomes either a run on
|
|
19
|
+
* another credential for the same model or an accurate error, instead of the
|
|
20
|
+
* agent 401ing three seconds in against a "rotate your GitHub Actions secret"
|
|
21
|
+
* CTA naming a place the user never put a key.
|
|
29
22
|
*
|
|
30
23
|
* Failed credentials are deleted from `process.env` only when this run can
|
|
31
24
|
* still proceed without them. Nothing downstream infers anything from that
|
|
@@ -38,5 +31,4 @@ export type CredentialOutcome = {
|
|
|
38
31
|
*/
|
|
39
32
|
export declare function checkConfiguredCredentials(params: {
|
|
40
33
|
model: string | undefined;
|
|
41
|
-
authorized: Set<string>;
|
|
42
34
|
}): Promise<CredentialOutcome>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { RunContextData } from "./runContextData.ts";
|
|
2
|
+
/**
|
|
3
|
+
* load one subscription per provider for model discovery; keep the pool unflattened. runs
|
|
4
|
+
* after the account secrets are injected: `injected` names the ones that came from storage,
|
|
5
|
+
* so everything else set here is a workflow credential.
|
|
6
|
+
*/
|
|
7
|
+
export declare function initializeCredentialPool(input: {
|
|
8
|
+
ctx: RunContextData;
|
|
9
|
+
slug: string | undefined;
|
|
10
|
+
injected: string[];
|
|
11
|
+
}): Promise<void>;
|
|
12
|
+
export declare function resolvePoolModel(input: {
|
|
13
|
+
codexAgent: boolean;
|
|
14
|
+
}): string | undefined;
|
|
15
|
+
export declare function selectConfiguredCredential(input: {
|
|
16
|
+
ctx: RunContextData;
|
|
17
|
+
model: string | undefined;
|
|
18
|
+
}): Promise<boolean>;
|
|
@@ -14,6 +14,10 @@ export declare function captureBaselineModels(cliPath: string): void;
|
|
|
14
14
|
* Codex auth.json are in place. Logs the diff against the baseline as
|
|
15
15
|
* `» BYOK auth enabled N model(s): …`. */
|
|
16
16
|
export declare function captureAuthorizedModels(cliPath: string): void;
|
|
17
|
+
/** A model whose credential the pool just probed. Added directly: a second
|
|
18
|
+
* `opencode models` would run inside the checkout and load the repo's own
|
|
19
|
+
* `.opencode` plugins, which can outlast its timeout and empty the set. */
|
|
20
|
+
export declare function authorizeModel(model: string): void;
|
|
17
21
|
/** Authorized set captured after Pullfrog-stored auth is applied. Throws if
|
|
18
22
|
* called before `captureAuthorizedModels` — the call sites (api-key
|
|
19
23
|
* validation, auto-select) all run strictly after capture. */
|
|
@@ -18,6 +18,11 @@ export declare const subscriptionSchema: z.ZodObject<{
|
|
|
18
18
|
inherited: z.ZodBoolean;
|
|
19
19
|
shared: z.ZodBoolean;
|
|
20
20
|
registeredAt: z.ZodString;
|
|
21
|
+
status: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
22
|
+
rejected: "rejected";
|
|
23
|
+
exhausted: "exhausted";
|
|
24
|
+
}>>>;
|
|
25
|
+
resetAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
21
26
|
}, z.core.$strip>;
|
|
22
27
|
export type Subscription = z.infer<typeof subscriptionSchema>;
|
|
23
28
|
export declare const subscriptionListSchema: z.ZodObject<{
|
|
@@ -34,6 +39,11 @@ export declare const subscriptionListSchema: z.ZodObject<{
|
|
|
34
39
|
inherited: z.ZodBoolean;
|
|
35
40
|
shared: z.ZodBoolean;
|
|
36
41
|
registeredAt: z.ZodString;
|
|
42
|
+
status: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
43
|
+
rejected: "rejected";
|
|
44
|
+
exhausted: "exhausted";
|
|
45
|
+
}>>>;
|
|
46
|
+
resetAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
37
47
|
}, z.core.$strip>>;
|
|
38
48
|
writable: z.ZodBoolean;
|
|
39
49
|
}, z.core.$strip>;
|
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
import type { SubscriptionName } from "./subscriptionCredentials.ts";
|
|
2
|
+
/**
|
|
3
|
+
* the provider's answer about one credential. only `rejected` and `exhausted`
|
|
4
|
+
* advance the chain; `unknown` never changes who pays.
|
|
5
|
+
*/
|
|
6
|
+
export type ProbeVerdict = {
|
|
7
|
+
status: "usable";
|
|
8
|
+
} | {
|
|
9
|
+
status: "unknown";
|
|
10
|
+
} | {
|
|
11
|
+
status: "rejected";
|
|
12
|
+
detail: string;
|
|
13
|
+
} | {
|
|
14
|
+
status: "exhausted";
|
|
15
|
+
detail: string;
|
|
16
|
+
resetAt: Date | undefined;
|
|
17
|
+
};
|
|
2
18
|
export declare class SubscriptionCredentialError extends Error {
|
|
3
19
|
}
|
|
4
20
|
export declare function subscriptionIdentity(input: {
|
|
@@ -13,10 +29,10 @@ export declare function probeSubscription(input: {
|
|
|
13
29
|
name: SubscriptionName;
|
|
14
30
|
value: string;
|
|
15
31
|
model?: string | undefined;
|
|
16
|
-
}): Promise<
|
|
32
|
+
}): Promise<ProbeVerdict>;
|
|
17
33
|
/** probe the chosen model, not a models-list permission a restricted key may lack. */
|
|
18
34
|
export declare function probeInference(input: {
|
|
19
35
|
name: string;
|
|
20
36
|
value: string;
|
|
21
37
|
model: string;
|
|
22
|
-
}): Promise<
|
|
38
|
+
}): Promise<ProbeVerdict>;
|