pullfrog 0.1.45 → 0.1.46
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/agents/opencodeShared.d.ts +25 -0
- package/dist/cli.mjs +174 -18
- package/dist/index.js +171 -15
- package/dist/internal.js +22 -0
- package/dist/models.d.ts +27 -1
- package/dist/utils/apiKeys.d.ts +10 -0
- package/dist/utils/runContext.d.ts +7 -0
- package/dist/utils/runContextData.d.ts +3 -0
- package/package.json +1 -1
|
@@ -16,6 +16,30 @@ export type OpenCodeConfig = {
|
|
|
16
16
|
export declare function geminiHighThinkingOverrides(): Record<string, {
|
|
17
17
|
options: object;
|
|
18
18
|
}>;
|
|
19
|
+
interface OpenAICompatibleProviderEntry {
|
|
20
|
+
npm: string;
|
|
21
|
+
name: string;
|
|
22
|
+
options: {
|
|
23
|
+
baseURL: string | undefined;
|
|
24
|
+
apiKey: string | undefined;
|
|
25
|
+
};
|
|
26
|
+
models: Record<string, {
|
|
27
|
+
name: string;
|
|
28
|
+
limit: ModelLimit;
|
|
29
|
+
}>;
|
|
30
|
+
}
|
|
31
|
+
interface ModelLimit {
|
|
32
|
+
context: number;
|
|
33
|
+
output: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* OpenAI-compatible provider block for OPENCODE_CONFIG_CONTENT. opencode has no
|
|
37
|
+
* native provider for an arbitrary gateway, so inject one via
|
|
38
|
+
* `@ai-sdk/openai-compatible` when the run targets `openai-compatible/<model>`;
|
|
39
|
+
* `{}` otherwise so the caller can spread it unconditionally. base URL + key are
|
|
40
|
+
* guaranteed present by `validateOpenAICompatibleSetup`.
|
|
41
|
+
*/
|
|
42
|
+
export declare function openAICompatibleProvider(model: string | undefined): Record<string, OpenAICompatibleProviderEntry>;
|
|
19
43
|
/**
|
|
20
44
|
* Build the `provider.openrouter.models[id].options` map that pins every Kimi
|
|
21
45
|
* K2 OpenRouter alias away from the Enforcer-less providers via OpenRouter's
|
|
@@ -75,3 +99,4 @@ export declare function installOpencodeCli(params: {
|
|
|
75
99
|
binPath: string;
|
|
76
100
|
}): Promise<string>;
|
|
77
101
|
export declare function autoSelectModel(): string | undefined;
|
|
102
|
+
export {};
|
package/dist/cli.mjs
CHANGED
|
@@ -102119,6 +102119,28 @@ var providers = {
|
|
|
102119
102119
|
}
|
|
102120
102120
|
}
|
|
102121
102121
|
}),
|
|
102122
|
+
"openai-compatible": provider({
|
|
102123
|
+
// "Custom" is the picker group, "OpenAI-compatible" the entry under it, so the
|
|
102124
|
+
// menu reads `Custom › OpenAI-compatible` and a second custom backend (a
|
|
102125
|
+
// different wire format, say) slots in beside it without a rename. the
|
|
102126
|
+
// provider KEY stays `openai-compatible` — it's the stored slug and the
|
|
102127
|
+
// `OPENAI_COMPATIBLE_*` env prefix, so this is display-only.
|
|
102128
|
+
displayName: "Custom",
|
|
102129
|
+
// bring-your-own generic OpenAI-compatible endpoint — Cloudflare AI Gateway,
|
|
102130
|
+
// Alibaba DashScope, self-hosted vLLM, or any compatible gateway. base URL +
|
|
102131
|
+
// key + model ID are all supplied via env; nothing is cataloged or bumped.
|
|
102132
|
+
envVars: ["OPENAI_COMPATIBLE_BASE_URL", "OPENAI_COMPATIBLE_API_KEY", "OPENAI_COMPATIBLE_MODEL"],
|
|
102133
|
+
models: {
|
|
102134
|
+
// single routing entry — the actual model ID is read from
|
|
102135
|
+
// OPENAI_COMPATIBLE_MODEL at run time and the provider is materialized
|
|
102136
|
+
// via `@ai-sdk/openai-compatible`.
|
|
102137
|
+
byok: {
|
|
102138
|
+
displayName: "OpenAI-compatible",
|
|
102139
|
+
resolve: "openai-compatible",
|
|
102140
|
+
routing: "openai-compatible"
|
|
102141
|
+
}
|
|
102142
|
+
}
|
|
102143
|
+
}),
|
|
102122
102144
|
openrouter: provider({
|
|
102123
102145
|
displayName: "OpenRouter",
|
|
102124
102146
|
envVars: ["OPENROUTER_API_KEY"],
|
|
@@ -102336,6 +102358,12 @@ var DEFAULT_PROXY_MODEL = defaultProxyAlias.openRouterResolve;
|
|
|
102336
102358
|
var defaultProxyDisplayName = defaultProxyAlias.displayName;
|
|
102337
102359
|
var BEDROCK_MODEL_ID_ENV = "BEDROCK_MODEL_ID";
|
|
102338
102360
|
var VERTEX_MODEL_ID_ENV = "VERTEX_MODEL_ID";
|
|
102361
|
+
var OPENAI_COMPATIBLE_PROVIDER = "openai-compatible";
|
|
102362
|
+
var OPENAI_COMPATIBLE_BASE_URL_ENV = "OPENAI_COMPATIBLE_BASE_URL";
|
|
102363
|
+
var OPENAI_COMPATIBLE_API_KEY_ENV = "OPENAI_COMPATIBLE_API_KEY";
|
|
102364
|
+
var OPENAI_COMPATIBLE_MODEL_ENV = "OPENAI_COMPATIBLE_MODEL";
|
|
102365
|
+
var OPENAI_COMPATIBLE_CONTEXT_ENV = "OPENAI_COMPATIBLE_CONTEXT";
|
|
102366
|
+
var OPENAI_COMPATIBLE_MAX_OUTPUT_ENV = "OPENAI_COMPATIBLE_MAX_OUTPUT";
|
|
102339
102367
|
function isBedrockAnthropicId(bedrockModelId) {
|
|
102340
102368
|
return bedrockModelId.toLowerCase().split(/[./:]/).includes("anthropic");
|
|
102341
102369
|
}
|
|
@@ -103022,7 +103050,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
103022
103050
|
// package.json
|
|
103023
103051
|
var package_default = {
|
|
103024
103052
|
name: "pullfrog",
|
|
103025
|
-
version: "0.1.
|
|
103053
|
+
version: "0.1.46",
|
|
103026
103054
|
type: "module",
|
|
103027
103055
|
bin: {
|
|
103028
103056
|
pullfrog: "dist/cli.mjs",
|
|
@@ -110769,8 +110797,9 @@ function readModels(cliPath) {
|
|
|
110769
110797
|
stdio: ["ignore", "pipe", "pipe"]
|
|
110770
110798
|
});
|
|
110771
110799
|
if (result.status !== 0) {
|
|
110772
|
-
|
|
110773
|
-
|
|
110800
|
+
const stderr = (result.stderr ?? "").replace(ANSI_PATTERN, "").trim();
|
|
110801
|
+
failure = stderr || result.error?.message;
|
|
110802
|
+
log.debug(`\xBB \`opencode models\` failed (${result.status}): ${failure}`);
|
|
110774
110803
|
return /* @__PURE__ */ new Set();
|
|
110775
110804
|
}
|
|
110776
110805
|
failure = void 0;
|
|
@@ -110828,6 +110857,29 @@ function geminiHighThinkingOverrides() {
|
|
|
110828
110857
|
])
|
|
110829
110858
|
);
|
|
110830
110859
|
}
|
|
110860
|
+
function openAICompatibleLimit() {
|
|
110861
|
+
return {
|
|
110862
|
+
context: Number(process.env[OPENAI_COMPATIBLE_CONTEXT_ENV]),
|
|
110863
|
+
output: Number(process.env[OPENAI_COMPATIBLE_MAX_OUTPUT_ENV])
|
|
110864
|
+
};
|
|
110865
|
+
}
|
|
110866
|
+
function openAICompatibleProvider(model) {
|
|
110867
|
+
const prefix = `${OPENAI_COMPATIBLE_PROVIDER}/`;
|
|
110868
|
+
if (!model?.startsWith(prefix)) return {};
|
|
110869
|
+
const modelId = model.slice(prefix.length);
|
|
110870
|
+
return {
|
|
110871
|
+
[OPENAI_COMPATIBLE_PROVIDER]: {
|
|
110872
|
+
npm: "@ai-sdk/openai-compatible",
|
|
110873
|
+
name: "OpenAI-compatible",
|
|
110874
|
+
options: {
|
|
110875
|
+
// trailing slash would double up against opencode's `/chat/completions` join
|
|
110876
|
+
baseURL: process.env[OPENAI_COMPATIBLE_BASE_URL_ENV]?.replace(/\/$/, ""),
|
|
110877
|
+
apiKey: process.env[OPENAI_COMPATIBLE_API_KEY_ENV]
|
|
110878
|
+
},
|
|
110879
|
+
models: { [modelId]: { name: modelId, limit: openAICompatibleLimit() } }
|
|
110880
|
+
}
|
|
110881
|
+
};
|
|
110882
|
+
}
|
|
110831
110883
|
var KIMI_ENFORCERLESS_PROVIDERS = ["siliconflow", "together"];
|
|
110832
110884
|
function kimiOpenRouterProviderOverrides() {
|
|
110833
110885
|
return Object.fromEntries(
|
|
@@ -110919,7 +110971,8 @@ function buildSecurityConfig(ctx, model) {
|
|
|
110919
110971
|
google: { models: geminiHighThinkingOverrides() },
|
|
110920
110972
|
openrouter: {
|
|
110921
110973
|
models: { ...deepseekHighEffortOverrides(), ...kimiOpenRouterProviderOverrides() }
|
|
110922
|
-
}
|
|
110974
|
+
},
|
|
110975
|
+
...openAICompatibleProvider(model)
|
|
110923
110976
|
}
|
|
110924
110977
|
};
|
|
110925
110978
|
if (model) {
|
|
@@ -165185,6 +165238,15 @@ function resolveSlug(slug2) {
|
|
|
165185
165238
|
}
|
|
165186
165239
|
return vertexId;
|
|
165187
165240
|
}
|
|
165241
|
+
if (alias?.routing === "openai-compatible") {
|
|
165242
|
+
const modelId = process.env[OPENAI_COMPATIBLE_MODEL_ENV]?.trim();
|
|
165243
|
+
if (!modelId) {
|
|
165244
|
+
throw new Error(
|
|
165245
|
+
`${OPENAI_COMPATIBLE_MODEL_ENV} env var is required when the model is set to "${slug2}". set it to the model ID served by your OpenAI-compatible endpoint (e.g. a Cloudflare AI Gateway or DashScope model). see https://docs.pullfrog.com/openai-compatible for setup.`
|
|
165246
|
+
);
|
|
165247
|
+
}
|
|
165248
|
+
return `${OPENAI_COMPATIBLE_PROVIDER}/${modelId}`;
|
|
165249
|
+
}
|
|
165188
165250
|
return resolveCliModel(slug2);
|
|
165189
165251
|
}
|
|
165190
165252
|
function resolveModel(ctx) {
|
|
@@ -165234,6 +165296,21 @@ function resolveAgent(ctx) {
|
|
|
165234
165296
|
|
|
165235
165297
|
// utils/apiKeys.ts
|
|
165236
165298
|
var MISSING_KEY_MARKER = "no API key found";
|
|
165299
|
+
var SECRETS_UNAVAILABLE_MARKER = "couldn't load your Pullfrog secrets";
|
|
165300
|
+
function buildKeyError(params) {
|
|
165301
|
+
return params.secretsUnavailable ? buildSecretsUnavailableError(params) : buildMissingApiKeyError(params);
|
|
165302
|
+
}
|
|
165303
|
+
function buildSecretsUnavailableError(params) {
|
|
165304
|
+
const settingsUrl = `${getApiUrl()}/console/${params.owner}/${params.name}`;
|
|
165305
|
+
const modelClause = params.model ? ` needed for \`${params.model}\`` : "";
|
|
165306
|
+
return [
|
|
165307
|
+
`**Pullfrog ${SECRETS_UNAVAILABLE_MARKER}${modelClause} on this run.** The key is still stored \u2014 the runner just couldn't fetch it, so this is a transient failure on our side, not a missing key.`,
|
|
165308
|
+
"",
|
|
165309
|
+
"**To fix:** re-run the job. If it keeps happening, tell us in Discord.",
|
|
165310
|
+
"",
|
|
165311
|
+
`[Model settings \u2192](${settingsUrl}) \xB7 [Setup docs \u2192](https://docs.pullfrog.com/keys) \xB7 [Ask in Discord \u2192](https://discord.gg/8y96raFg8e)`
|
|
165312
|
+
].join("\n");
|
|
165313
|
+
}
|
|
165237
165314
|
function buildMissingApiKeyError(params) {
|
|
165238
165315
|
const githubSecretsUrl = `https://github.com/${params.owner}/${params.name}/settings/secrets/actions`;
|
|
165239
165316
|
const settingsUrl = `${getApiUrl()}/console/${params.owner}/${params.name}`;
|
|
@@ -165276,10 +165353,47 @@ add the missing secret(s) to your GitHub repository at ${githubSecretsUrl}, then
|
|
|
165276
165353
|
|
|
165277
165354
|
for full setup instructions, see https://docs.pullfrog.com/vertex`;
|
|
165278
165355
|
}
|
|
165356
|
+
function buildOpenAICompatibleSetupError(params) {
|
|
165357
|
+
const githubSecretsUrl = `https://github.com/${params.owner}/${params.name}/settings/secrets/actions`;
|
|
165358
|
+
return `OpenAI-compatible model selected but required configuration is missing: ${params.missing.join(", ")}.
|
|
165359
|
+
|
|
165360
|
+
only the API key is sensitive \u2014 add it as a secret at ${githubSecretsUrl}. everything else is plain workflow \`env:\`:
|
|
165361
|
+
|
|
165362
|
+
${OPENAI_COMPATIBLE_BASE_URL_ENV}: https://your-endpoint.example.com/v1
|
|
165363
|
+
${OPENAI_COMPATIBLE_API_KEY_ENV}: \${{ secrets.${OPENAI_COMPATIBLE_API_KEY_ENV} }}
|
|
165364
|
+
${OPENAI_COMPATIBLE_MODEL_ENV}: <model-id>
|
|
165365
|
+
${OPENAI_COMPATIBLE_CONTEXT_ENV}: "128000"
|
|
165366
|
+
${OPENAI_COMPATIBLE_MAX_OUTPUT_ENV}: "16384"
|
|
165367
|
+
|
|
165368
|
+
set the last two to the real limits of the model your endpoint serves. Pullfrog can't
|
|
165369
|
+
discover them \u2014 your endpoint owns the model catalog \u2014 and without them completions are
|
|
165370
|
+
capped at 32000 tokens (rejected outright by models with a smaller cap) and
|
|
165371
|
+
auto-compaction is disabled, so long runs grow until your endpoint refuses them.
|
|
165372
|
+
|
|
165373
|
+
for full setup instructions, see https://docs.pullfrog.com/openai-compatible`;
|
|
165374
|
+
}
|
|
165279
165375
|
function hasEnvVar3(name) {
|
|
165280
165376
|
const value2 = process.env[name];
|
|
165281
165377
|
return typeof value2 === "string" && value2.length > 0;
|
|
165282
165378
|
}
|
|
165379
|
+
function hasPositiveNumberEnvVar(name) {
|
|
165380
|
+
return Number(process.env[name]) > 0;
|
|
165381
|
+
}
|
|
165382
|
+
function validateOpenAICompatibleSetup(params) {
|
|
165383
|
+
const missing = [];
|
|
165384
|
+
if (!hasEnvVar3(OPENAI_COMPATIBLE_BASE_URL_ENV)) missing.push(OPENAI_COMPATIBLE_BASE_URL_ENV);
|
|
165385
|
+
if (!hasEnvVar3(OPENAI_COMPATIBLE_API_KEY_ENV)) missing.push(OPENAI_COMPATIBLE_API_KEY_ENV);
|
|
165386
|
+
if (!hasEnvVar3(OPENAI_COMPATIBLE_MODEL_ENV)) missing.push(OPENAI_COMPATIBLE_MODEL_ENV);
|
|
165387
|
+
if (!hasPositiveNumberEnvVar(OPENAI_COMPATIBLE_CONTEXT_ENV))
|
|
165388
|
+
missing.push(OPENAI_COMPATIBLE_CONTEXT_ENV);
|
|
165389
|
+
if (!hasPositiveNumberEnvVar(OPENAI_COMPATIBLE_MAX_OUTPUT_ENV))
|
|
165390
|
+
missing.push(OPENAI_COMPATIBLE_MAX_OUTPUT_ENV);
|
|
165391
|
+
if (missing.length > 0) {
|
|
165392
|
+
throw new Error(
|
|
165393
|
+
buildOpenAICompatibleSetupError({ owner: params.owner, name: params.name, missing })
|
|
165394
|
+
);
|
|
165395
|
+
}
|
|
165396
|
+
}
|
|
165283
165397
|
function validateBedrockSetup(params) {
|
|
165284
165398
|
const hasAuth = hasEnvVar3("AWS_BEARER_TOKEN_BEDROCK") || hasEnvVar3("AWS_ACCESS_KEY_ID") && hasEnvVar3("AWS_SECRET_ACCESS_KEY");
|
|
165285
165399
|
const missing = [];
|
|
@@ -165314,6 +165428,10 @@ function validateAgentApiKey(params) {
|
|
|
165314
165428
|
validateVertexSetup({ owner: params.owner, name: params.name });
|
|
165315
165429
|
return;
|
|
165316
165430
|
}
|
|
165431
|
+
if (params.model.startsWith(`${OPENAI_COMPATIBLE_PROVIDER}/`)) {
|
|
165432
|
+
validateOpenAICompatibleSetup({ owner: params.owner, name: params.name });
|
|
165433
|
+
return;
|
|
165434
|
+
}
|
|
165317
165435
|
if (!params.model.includes("/")) {
|
|
165318
165436
|
if (process.env[VERTEX_MODEL_ID_ENV]?.trim() === params.model) {
|
|
165319
165437
|
validateVertexSetup({ owner: params.owner, name: params.name });
|
|
@@ -165326,23 +165444,46 @@ function validateAgentApiKey(params) {
|
|
|
165326
165444
|
if (params.authorized.has(params.model)) return;
|
|
165327
165445
|
const reason = getModelsFailure();
|
|
165328
165446
|
if (reason) throw new Error(reason);
|
|
165447
|
+
if (getModelEnvVars(params.model).some(hasEnvVar3)) return;
|
|
165329
165448
|
throw new Error(
|
|
165330
|
-
|
|
165449
|
+
buildKeyError({
|
|
165450
|
+
owner: params.owner,
|
|
165451
|
+
name: params.name,
|
|
165452
|
+
model: params.model,
|
|
165453
|
+
secretsUnavailable: params.secretsUnavailable
|
|
165454
|
+
})
|
|
165331
165455
|
);
|
|
165332
165456
|
}
|
|
165333
165457
|
if (hasEnvVar3("ANTHROPIC_API_KEY") || hasEnvVar3("CLAUDE_CODE_OAUTH_TOKEN")) return;
|
|
165334
165458
|
throw new Error(
|
|
165335
|
-
|
|
165459
|
+
buildKeyError({
|
|
165460
|
+
owner: params.owner,
|
|
165461
|
+
name: params.name,
|
|
165462
|
+
model: params.model,
|
|
165463
|
+
secretsUnavailable: params.secretsUnavailable
|
|
165464
|
+
})
|
|
165336
165465
|
);
|
|
165337
165466
|
}
|
|
165338
165467
|
if (params.agent.name === "opencode") {
|
|
165339
165468
|
if (params.authorized.size > 0) return;
|
|
165340
165469
|
const reason = getModelsFailure();
|
|
165341
165470
|
if (reason) throw new Error(reason);
|
|
165342
|
-
throw new Error(
|
|
165471
|
+
throw new Error(
|
|
165472
|
+
buildKeyError({
|
|
165473
|
+
owner: params.owner,
|
|
165474
|
+
name: params.name,
|
|
165475
|
+
secretsUnavailable: params.secretsUnavailable
|
|
165476
|
+
})
|
|
165477
|
+
);
|
|
165343
165478
|
}
|
|
165344
165479
|
if (hasEnvVar3("ANTHROPIC_API_KEY") || hasEnvVar3("CLAUDE_CODE_OAUTH_TOKEN")) return;
|
|
165345
|
-
throw new Error(
|
|
165480
|
+
throw new Error(
|
|
165481
|
+
buildKeyError({
|
|
165482
|
+
owner: params.owner,
|
|
165483
|
+
name: params.name,
|
|
165484
|
+
secretsUnavailable: params.secretsUnavailable
|
|
165485
|
+
})
|
|
165486
|
+
);
|
|
165346
165487
|
}
|
|
165347
165488
|
function isApiKeyAuthError(text) {
|
|
165348
165489
|
if (!text) return false;
|
|
@@ -166027,7 +166168,7 @@ async function persistXrepoLearnings(ctx) {
|
|
|
166027
166168
|
// utils/modelAccess.ts
|
|
166028
166169
|
function decideModelAccess(input) {
|
|
166029
166170
|
if (!input.modelExplicit || !input.model) return { kind: "ok" };
|
|
166030
|
-
const byokAuthorized = !!input.resolvedModel && (input.authorized.has(input.resolvedModel) || !input.resolvedModel.includes("/"));
|
|
166171
|
+
const byokAuthorized = !!input.resolvedModel && (input.authorized.has(input.resolvedModel) || !input.resolvedModel.includes("/") || input.resolvedModel.startsWith(`${OPENAI_COMPATIBLE_PROVIDER}/`));
|
|
166031
166172
|
if (input.proxyActive) {
|
|
166032
166173
|
const target = resolveOpenRouterModel(input.model);
|
|
166033
166174
|
if (input.oss) {
|
|
@@ -166836,6 +166977,10 @@ var defaultRunContext = {
|
|
|
166836
166977
|
oss: false,
|
|
166837
166978
|
plan: "none"
|
|
166838
166979
|
};
|
|
166980
|
+
var unknownSecretsRunContext = {
|
|
166981
|
+
...defaultRunContext,
|
|
166982
|
+
secretsUnavailable: true
|
|
166983
|
+
};
|
|
166839
166984
|
async function fetchRunContext(params) {
|
|
166840
166985
|
const timeoutMs = 3e4;
|
|
166841
166986
|
const controller = new AbortController();
|
|
@@ -166854,7 +166999,7 @@ async function fetchRunContext(params) {
|
|
|
166854
166999
|
});
|
|
166855
167000
|
clearTimeout(timeoutId);
|
|
166856
167001
|
if (!response.ok) {
|
|
166857
|
-
return defaultRunContext;
|
|
167002
|
+
return response.status >= 500 ? unknownSecretsRunContext : defaultRunContext;
|
|
166858
167003
|
}
|
|
166859
167004
|
const data = await response.json();
|
|
166860
167005
|
if (data === null) {
|
|
@@ -166878,11 +167023,12 @@ async function fetchRunContext(params) {
|
|
|
166878
167023
|
oss: data.oss ?? false,
|
|
166879
167024
|
plan: data.plan ?? "none",
|
|
166880
167025
|
proxyModel: data.proxyModel,
|
|
166881
|
-
dbSecrets: data.dbSecrets
|
|
167026
|
+
dbSecrets: data.dbSecrets,
|
|
167027
|
+
secretsUnavailable: data.secretsUnavailable
|
|
166882
167028
|
};
|
|
166883
167029
|
} catch {
|
|
166884
167030
|
clearTimeout(timeoutId);
|
|
166885
|
-
return
|
|
167031
|
+
return unknownSecretsRunContext;
|
|
166886
167032
|
}
|
|
166887
167033
|
}
|
|
166888
167034
|
|
|
@@ -166904,7 +167050,10 @@ async function resolveRunContextData(params) {
|
|
|
166904
167050
|
const repoContext = parseRepoContext();
|
|
166905
167051
|
let oidcToken;
|
|
166906
167052
|
try {
|
|
166907
|
-
oidcToken = await core9.getIDToken("pullfrog-api")
|
|
167053
|
+
oidcToken = await op(() => core9.getIDToken("pullfrog-api"), {
|
|
167054
|
+
name: "OIDC mint",
|
|
167055
|
+
retries: process.env.ACTIONS_ID_TOKEN_REQUEST_URL ? [200, 1e3] : []
|
|
167056
|
+
})();
|
|
166908
167057
|
} catch {
|
|
166909
167058
|
}
|
|
166910
167059
|
const [repoResponse, runContext] = await Promise.all([
|
|
@@ -166926,7 +167075,10 @@ async function resolveRunContextData(params) {
|
|
|
166926
167075
|
oss: runContext.oss,
|
|
166927
167076
|
plan: runContext.plan,
|
|
166928
167077
|
proxyModel: runContext.proxyModel,
|
|
166929
|
-
dbSecrets: runContext.dbSecrets
|
|
167078
|
+
dbSecrets: runContext.dbSecrets,
|
|
167079
|
+
// a failed mint on a runner that should have been able to mint is the same
|
|
167080
|
+
// outcome as the server-side failure: the run never sees stored secrets.
|
|
167081
|
+
secretsUnavailable: runContext.secretsUnavailable || !!process.env.ACTIONS_ID_TOKEN_REQUEST_URL && oidcToken === void 0
|
|
166930
167082
|
};
|
|
166931
167083
|
}
|
|
166932
167084
|
|
|
@@ -167091,6 +167243,9 @@ function renderRunError(input) {
|
|
|
167091
167243
|
if (input.errorMessage.includes(MODEL_ACCESS_MARKER)) {
|
|
167092
167244
|
return { summary: input.errorMessage, comment: input.errorMessage };
|
|
167093
167245
|
}
|
|
167246
|
+
if (input.errorMessage.includes(SECRETS_UNAVAILABLE_MARKER)) {
|
|
167247
|
+
return { summary: input.errorMessage, comment: input.errorMessage };
|
|
167248
|
+
}
|
|
167094
167249
|
const isHang = input.errorMessage.startsWith("activity timeout") || input.errorMessage.startsWith("agent still pending");
|
|
167095
167250
|
const hangBody = isHang ? formatAgentHangBody({
|
|
167096
167251
|
diagnostic: input.agentDiagnostic,
|
|
@@ -167825,7 +167980,8 @@ async function main() {
|
|
|
167825
167980
|
model: effectiveModel,
|
|
167826
167981
|
authorized: getAuthorizedModels(),
|
|
167827
167982
|
owner: runContext.repo.owner,
|
|
167828
|
-
name: runContext.repo.name
|
|
167983
|
+
name: runContext.repo.name,
|
|
167984
|
+
secretsUnavailable: runContext.secretsUnavailable
|
|
167829
167985
|
});
|
|
167830
167986
|
}
|
|
167831
167987
|
await setupGit({
|
|
@@ -168322,7 +168478,7 @@ function link(text, url4) {
|
|
|
168322
168478
|
return `\x1B]8;;${url4}\x07${text}\x1B]8;;\x07`;
|
|
168323
168479
|
}
|
|
168324
168480
|
function buildProviders() {
|
|
168325
|
-
return Object.entries(providers).filter(([key]) => key !== "opencode" && key !== "openrouter"
|
|
168481
|
+
return Object.entries(providers).filter(([key]) => key !== "opencode" && key !== "openrouter").map(([key, config3]) => {
|
|
168326
168482
|
const aliases = modelAliases.filter(
|
|
168327
168483
|
(a) => a.provider === key && !a.fallback && !a.routing && !a.hidden
|
|
168328
168484
|
);
|
|
@@ -168342,7 +168498,7 @@ function buildProviders() {
|
|
|
168342
168498
|
hint: a === recommended ? "recommended" : void 0
|
|
168343
168499
|
}))
|
|
168344
168500
|
};
|
|
168345
|
-
});
|
|
168501
|
+
}).filter((p2) => p2.models.length > 0);
|
|
168346
168502
|
}
|
|
168347
168503
|
var CLI_PROVIDERS = buildProviders();
|
|
168348
168504
|
function resolveModelProvider(slug2) {
|
|
@@ -169152,7 +169308,7 @@ async function runCli4(input) {
|
|
|
169152
169308
|
}
|
|
169153
169309
|
|
|
169154
169310
|
// cli.ts
|
|
169155
|
-
var VERSION10 = "0.1.
|
|
169311
|
+
var VERSION10 = "0.1.46";
|
|
169156
169312
|
var bin = basename2(process.argv[1] || "");
|
|
169157
169313
|
var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
|
|
169158
169314
|
var rawArgs = process.argv.slice(2);
|
package/dist/index.js
CHANGED
|
@@ -100190,6 +100190,28 @@ var providers = {
|
|
|
100190
100190
|
}
|
|
100191
100191
|
}
|
|
100192
100192
|
}),
|
|
100193
|
+
"openai-compatible": provider({
|
|
100194
|
+
// "Custom" is the picker group, "OpenAI-compatible" the entry under it, so the
|
|
100195
|
+
// menu reads `Custom › OpenAI-compatible` and a second custom backend (a
|
|
100196
|
+
// different wire format, say) slots in beside it without a rename. the
|
|
100197
|
+
// provider KEY stays `openai-compatible` — it's the stored slug and the
|
|
100198
|
+
// `OPENAI_COMPATIBLE_*` env prefix, so this is display-only.
|
|
100199
|
+
displayName: "Custom",
|
|
100200
|
+
// bring-your-own generic OpenAI-compatible endpoint — Cloudflare AI Gateway,
|
|
100201
|
+
// Alibaba DashScope, self-hosted vLLM, or any compatible gateway. base URL +
|
|
100202
|
+
// key + model ID are all supplied via env; nothing is cataloged or bumped.
|
|
100203
|
+
envVars: ["OPENAI_COMPATIBLE_BASE_URL", "OPENAI_COMPATIBLE_API_KEY", "OPENAI_COMPATIBLE_MODEL"],
|
|
100204
|
+
models: {
|
|
100205
|
+
// single routing entry — the actual model ID is read from
|
|
100206
|
+
// OPENAI_COMPATIBLE_MODEL at run time and the provider is materialized
|
|
100207
|
+
// via `@ai-sdk/openai-compatible`.
|
|
100208
|
+
byok: {
|
|
100209
|
+
displayName: "OpenAI-compatible",
|
|
100210
|
+
resolve: "openai-compatible",
|
|
100211
|
+
routing: "openai-compatible"
|
|
100212
|
+
}
|
|
100213
|
+
}
|
|
100214
|
+
}),
|
|
100193
100215
|
openrouter: provider({
|
|
100194
100216
|
displayName: "OpenRouter",
|
|
100195
100217
|
envVars: ["OPENROUTER_API_KEY"],
|
|
@@ -100407,6 +100429,12 @@ var DEFAULT_PROXY_MODEL = defaultProxyAlias.openRouterResolve;
|
|
|
100407
100429
|
var defaultProxyDisplayName = defaultProxyAlias.displayName;
|
|
100408
100430
|
var BEDROCK_MODEL_ID_ENV = "BEDROCK_MODEL_ID";
|
|
100409
100431
|
var VERTEX_MODEL_ID_ENV = "VERTEX_MODEL_ID";
|
|
100432
|
+
var OPENAI_COMPATIBLE_PROVIDER = "openai-compatible";
|
|
100433
|
+
var OPENAI_COMPATIBLE_BASE_URL_ENV = "OPENAI_COMPATIBLE_BASE_URL";
|
|
100434
|
+
var OPENAI_COMPATIBLE_API_KEY_ENV = "OPENAI_COMPATIBLE_API_KEY";
|
|
100435
|
+
var OPENAI_COMPATIBLE_MODEL_ENV = "OPENAI_COMPATIBLE_MODEL";
|
|
100436
|
+
var OPENAI_COMPATIBLE_CONTEXT_ENV = "OPENAI_COMPATIBLE_CONTEXT";
|
|
100437
|
+
var OPENAI_COMPATIBLE_MAX_OUTPUT_ENV = "OPENAI_COMPATIBLE_MAX_OUTPUT";
|
|
100410
100438
|
function isBedrockAnthropicId(bedrockModelId) {
|
|
100411
100439
|
return bedrockModelId.toLowerCase().split(/[./:]/).includes("anthropic");
|
|
100412
100440
|
}
|
|
@@ -101093,7 +101121,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
101093
101121
|
// package.json
|
|
101094
101122
|
var package_default = {
|
|
101095
101123
|
name: "pullfrog",
|
|
101096
|
-
version: "0.1.
|
|
101124
|
+
version: "0.1.46",
|
|
101097
101125
|
type: "module",
|
|
101098
101126
|
bin: {
|
|
101099
101127
|
pullfrog: "dist/cli.mjs",
|
|
@@ -108882,8 +108910,9 @@ function readModels(cliPath) {
|
|
|
108882
108910
|
stdio: ["ignore", "pipe", "pipe"]
|
|
108883
108911
|
});
|
|
108884
108912
|
if (result.status !== 0) {
|
|
108885
|
-
|
|
108886
|
-
|
|
108913
|
+
const stderr = (result.stderr ?? "").replace(ANSI_PATTERN, "").trim();
|
|
108914
|
+
failure = stderr || result.error?.message;
|
|
108915
|
+
log.debug(`\xBB \`opencode models\` failed (${result.status}): ${failure}`);
|
|
108887
108916
|
return /* @__PURE__ */ new Set();
|
|
108888
108917
|
}
|
|
108889
108918
|
failure = void 0;
|
|
@@ -108941,6 +108970,29 @@ function geminiHighThinkingOverrides() {
|
|
|
108941
108970
|
])
|
|
108942
108971
|
);
|
|
108943
108972
|
}
|
|
108973
|
+
function openAICompatibleLimit() {
|
|
108974
|
+
return {
|
|
108975
|
+
context: Number(process.env[OPENAI_COMPATIBLE_CONTEXT_ENV]),
|
|
108976
|
+
output: Number(process.env[OPENAI_COMPATIBLE_MAX_OUTPUT_ENV])
|
|
108977
|
+
};
|
|
108978
|
+
}
|
|
108979
|
+
function openAICompatibleProvider(model) {
|
|
108980
|
+
const prefix = `${OPENAI_COMPATIBLE_PROVIDER}/`;
|
|
108981
|
+
if (!model?.startsWith(prefix)) return {};
|
|
108982
|
+
const modelId = model.slice(prefix.length);
|
|
108983
|
+
return {
|
|
108984
|
+
[OPENAI_COMPATIBLE_PROVIDER]: {
|
|
108985
|
+
npm: "@ai-sdk/openai-compatible",
|
|
108986
|
+
name: "OpenAI-compatible",
|
|
108987
|
+
options: {
|
|
108988
|
+
// trailing slash would double up against opencode's `/chat/completions` join
|
|
108989
|
+
baseURL: process.env[OPENAI_COMPATIBLE_BASE_URL_ENV]?.replace(/\/$/, ""),
|
|
108990
|
+
apiKey: process.env[OPENAI_COMPATIBLE_API_KEY_ENV]
|
|
108991
|
+
},
|
|
108992
|
+
models: { [modelId]: { name: modelId, limit: openAICompatibleLimit() } }
|
|
108993
|
+
}
|
|
108994
|
+
};
|
|
108995
|
+
}
|
|
108944
108996
|
var KIMI_ENFORCERLESS_PROVIDERS = ["siliconflow", "together"];
|
|
108945
108997
|
function kimiOpenRouterProviderOverrides() {
|
|
108946
108998
|
return Object.fromEntries(
|
|
@@ -109032,7 +109084,8 @@ function buildSecurityConfig(ctx, model) {
|
|
|
109032
109084
|
google: { models: geminiHighThinkingOverrides() },
|
|
109033
109085
|
openrouter: {
|
|
109034
109086
|
models: { ...deepseekHighEffortOverrides(), ...kimiOpenRouterProviderOverrides() }
|
|
109035
|
-
}
|
|
109087
|
+
},
|
|
109088
|
+
...openAICompatibleProvider(model)
|
|
109036
109089
|
}
|
|
109037
109090
|
};
|
|
109038
109091
|
if (model) {
|
|
@@ -163298,6 +163351,15 @@ function resolveSlug(slug2) {
|
|
|
163298
163351
|
}
|
|
163299
163352
|
return vertexId;
|
|
163300
163353
|
}
|
|
163354
|
+
if (alias?.routing === "openai-compatible") {
|
|
163355
|
+
const modelId = process.env[OPENAI_COMPATIBLE_MODEL_ENV]?.trim();
|
|
163356
|
+
if (!modelId) {
|
|
163357
|
+
throw new Error(
|
|
163358
|
+
`${OPENAI_COMPATIBLE_MODEL_ENV} env var is required when the model is set to "${slug2}". set it to the model ID served by your OpenAI-compatible endpoint (e.g. a Cloudflare AI Gateway or DashScope model). see https://docs.pullfrog.com/openai-compatible for setup.`
|
|
163359
|
+
);
|
|
163360
|
+
}
|
|
163361
|
+
return `${OPENAI_COMPATIBLE_PROVIDER}/${modelId}`;
|
|
163362
|
+
}
|
|
163301
163363
|
return resolveCliModel(slug2);
|
|
163302
163364
|
}
|
|
163303
163365
|
function resolveModel(ctx) {
|
|
@@ -163347,6 +163409,21 @@ function resolveAgent(ctx) {
|
|
|
163347
163409
|
|
|
163348
163410
|
// utils/apiKeys.ts
|
|
163349
163411
|
var MISSING_KEY_MARKER = "no API key found";
|
|
163412
|
+
var SECRETS_UNAVAILABLE_MARKER = "couldn't load your Pullfrog secrets";
|
|
163413
|
+
function buildKeyError(params) {
|
|
163414
|
+
return params.secretsUnavailable ? buildSecretsUnavailableError(params) : buildMissingApiKeyError(params);
|
|
163415
|
+
}
|
|
163416
|
+
function buildSecretsUnavailableError(params) {
|
|
163417
|
+
const settingsUrl = `${getApiUrl()}/console/${params.owner}/${params.name}`;
|
|
163418
|
+
const modelClause = params.model ? ` needed for \`${params.model}\`` : "";
|
|
163419
|
+
return [
|
|
163420
|
+
`**Pullfrog ${SECRETS_UNAVAILABLE_MARKER}${modelClause} on this run.** The key is still stored \u2014 the runner just couldn't fetch it, so this is a transient failure on our side, not a missing key.`,
|
|
163421
|
+
"",
|
|
163422
|
+
"**To fix:** re-run the job. If it keeps happening, tell us in Discord.",
|
|
163423
|
+
"",
|
|
163424
|
+
`[Model settings \u2192](${settingsUrl}) \xB7 [Setup docs \u2192](https://docs.pullfrog.com/keys) \xB7 [Ask in Discord \u2192](https://discord.gg/8y96raFg8e)`
|
|
163425
|
+
].join("\n");
|
|
163426
|
+
}
|
|
163350
163427
|
function buildMissingApiKeyError(params) {
|
|
163351
163428
|
const githubSecretsUrl = `https://github.com/${params.owner}/${params.name}/settings/secrets/actions`;
|
|
163352
163429
|
const settingsUrl = `${getApiUrl()}/console/${params.owner}/${params.name}`;
|
|
@@ -163389,10 +163466,47 @@ add the missing secret(s) to your GitHub repository at ${githubSecretsUrl}, then
|
|
|
163389
163466
|
|
|
163390
163467
|
for full setup instructions, see https://docs.pullfrog.com/vertex`;
|
|
163391
163468
|
}
|
|
163469
|
+
function buildOpenAICompatibleSetupError(params) {
|
|
163470
|
+
const githubSecretsUrl = `https://github.com/${params.owner}/${params.name}/settings/secrets/actions`;
|
|
163471
|
+
return `OpenAI-compatible model selected but required configuration is missing: ${params.missing.join(", ")}.
|
|
163472
|
+
|
|
163473
|
+
only the API key is sensitive \u2014 add it as a secret at ${githubSecretsUrl}. everything else is plain workflow \`env:\`:
|
|
163474
|
+
|
|
163475
|
+
${OPENAI_COMPATIBLE_BASE_URL_ENV}: https://your-endpoint.example.com/v1
|
|
163476
|
+
${OPENAI_COMPATIBLE_API_KEY_ENV}: \${{ secrets.${OPENAI_COMPATIBLE_API_KEY_ENV} }}
|
|
163477
|
+
${OPENAI_COMPATIBLE_MODEL_ENV}: <model-id>
|
|
163478
|
+
${OPENAI_COMPATIBLE_CONTEXT_ENV}: "128000"
|
|
163479
|
+
${OPENAI_COMPATIBLE_MAX_OUTPUT_ENV}: "16384"
|
|
163480
|
+
|
|
163481
|
+
set the last two to the real limits of the model your endpoint serves. Pullfrog can't
|
|
163482
|
+
discover them \u2014 your endpoint owns the model catalog \u2014 and without them completions are
|
|
163483
|
+
capped at 32000 tokens (rejected outright by models with a smaller cap) and
|
|
163484
|
+
auto-compaction is disabled, so long runs grow until your endpoint refuses them.
|
|
163485
|
+
|
|
163486
|
+
for full setup instructions, see https://docs.pullfrog.com/openai-compatible`;
|
|
163487
|
+
}
|
|
163392
163488
|
function hasEnvVar3(name) {
|
|
163393
163489
|
const value2 = process.env[name];
|
|
163394
163490
|
return typeof value2 === "string" && value2.length > 0;
|
|
163395
163491
|
}
|
|
163492
|
+
function hasPositiveNumberEnvVar(name) {
|
|
163493
|
+
return Number(process.env[name]) > 0;
|
|
163494
|
+
}
|
|
163495
|
+
function validateOpenAICompatibleSetup(params) {
|
|
163496
|
+
const missing = [];
|
|
163497
|
+
if (!hasEnvVar3(OPENAI_COMPATIBLE_BASE_URL_ENV)) missing.push(OPENAI_COMPATIBLE_BASE_URL_ENV);
|
|
163498
|
+
if (!hasEnvVar3(OPENAI_COMPATIBLE_API_KEY_ENV)) missing.push(OPENAI_COMPATIBLE_API_KEY_ENV);
|
|
163499
|
+
if (!hasEnvVar3(OPENAI_COMPATIBLE_MODEL_ENV)) missing.push(OPENAI_COMPATIBLE_MODEL_ENV);
|
|
163500
|
+
if (!hasPositiveNumberEnvVar(OPENAI_COMPATIBLE_CONTEXT_ENV))
|
|
163501
|
+
missing.push(OPENAI_COMPATIBLE_CONTEXT_ENV);
|
|
163502
|
+
if (!hasPositiveNumberEnvVar(OPENAI_COMPATIBLE_MAX_OUTPUT_ENV))
|
|
163503
|
+
missing.push(OPENAI_COMPATIBLE_MAX_OUTPUT_ENV);
|
|
163504
|
+
if (missing.length > 0) {
|
|
163505
|
+
throw new Error(
|
|
163506
|
+
buildOpenAICompatibleSetupError({ owner: params.owner, name: params.name, missing })
|
|
163507
|
+
);
|
|
163508
|
+
}
|
|
163509
|
+
}
|
|
163396
163510
|
function validateBedrockSetup(params) {
|
|
163397
163511
|
const hasAuth = hasEnvVar3("AWS_BEARER_TOKEN_BEDROCK") || hasEnvVar3("AWS_ACCESS_KEY_ID") && hasEnvVar3("AWS_SECRET_ACCESS_KEY");
|
|
163398
163512
|
const missing = [];
|
|
@@ -163427,6 +163541,10 @@ function validateAgentApiKey(params) {
|
|
|
163427
163541
|
validateVertexSetup({ owner: params.owner, name: params.name });
|
|
163428
163542
|
return;
|
|
163429
163543
|
}
|
|
163544
|
+
if (params.model.startsWith(`${OPENAI_COMPATIBLE_PROVIDER}/`)) {
|
|
163545
|
+
validateOpenAICompatibleSetup({ owner: params.owner, name: params.name });
|
|
163546
|
+
return;
|
|
163547
|
+
}
|
|
163430
163548
|
if (!params.model.includes("/")) {
|
|
163431
163549
|
if (process.env[VERTEX_MODEL_ID_ENV]?.trim() === params.model) {
|
|
163432
163550
|
validateVertexSetup({ owner: params.owner, name: params.name });
|
|
@@ -163439,23 +163557,46 @@ function validateAgentApiKey(params) {
|
|
|
163439
163557
|
if (params.authorized.has(params.model)) return;
|
|
163440
163558
|
const reason = getModelsFailure();
|
|
163441
163559
|
if (reason) throw new Error(reason);
|
|
163560
|
+
if (getModelEnvVars(params.model).some(hasEnvVar3)) return;
|
|
163442
163561
|
throw new Error(
|
|
163443
|
-
|
|
163562
|
+
buildKeyError({
|
|
163563
|
+
owner: params.owner,
|
|
163564
|
+
name: params.name,
|
|
163565
|
+
model: params.model,
|
|
163566
|
+
secretsUnavailable: params.secretsUnavailable
|
|
163567
|
+
})
|
|
163444
163568
|
);
|
|
163445
163569
|
}
|
|
163446
163570
|
if (hasEnvVar3("ANTHROPIC_API_KEY") || hasEnvVar3("CLAUDE_CODE_OAUTH_TOKEN")) return;
|
|
163447
163571
|
throw new Error(
|
|
163448
|
-
|
|
163572
|
+
buildKeyError({
|
|
163573
|
+
owner: params.owner,
|
|
163574
|
+
name: params.name,
|
|
163575
|
+
model: params.model,
|
|
163576
|
+
secretsUnavailable: params.secretsUnavailable
|
|
163577
|
+
})
|
|
163449
163578
|
);
|
|
163450
163579
|
}
|
|
163451
163580
|
if (params.agent.name === "opencode") {
|
|
163452
163581
|
if (params.authorized.size > 0) return;
|
|
163453
163582
|
const reason = getModelsFailure();
|
|
163454
163583
|
if (reason) throw new Error(reason);
|
|
163455
|
-
throw new Error(
|
|
163584
|
+
throw new Error(
|
|
163585
|
+
buildKeyError({
|
|
163586
|
+
owner: params.owner,
|
|
163587
|
+
name: params.name,
|
|
163588
|
+
secretsUnavailable: params.secretsUnavailable
|
|
163589
|
+
})
|
|
163590
|
+
);
|
|
163456
163591
|
}
|
|
163457
163592
|
if (hasEnvVar3("ANTHROPIC_API_KEY") || hasEnvVar3("CLAUDE_CODE_OAUTH_TOKEN")) return;
|
|
163458
|
-
throw new Error(
|
|
163593
|
+
throw new Error(
|
|
163594
|
+
buildKeyError({
|
|
163595
|
+
owner: params.owner,
|
|
163596
|
+
name: params.name,
|
|
163597
|
+
secretsUnavailable: params.secretsUnavailable
|
|
163598
|
+
})
|
|
163599
|
+
);
|
|
163459
163600
|
}
|
|
163460
163601
|
function isApiKeyAuthError(text) {
|
|
163461
163602
|
if (!text) return false;
|
|
@@ -164140,7 +164281,7 @@ async function persistXrepoLearnings(ctx) {
|
|
|
164140
164281
|
// utils/modelAccess.ts
|
|
164141
164282
|
function decideModelAccess(input) {
|
|
164142
164283
|
if (!input.modelExplicit || !input.model) return { kind: "ok" };
|
|
164143
|
-
const byokAuthorized = !!input.resolvedModel && (input.authorized.has(input.resolvedModel) || !input.resolvedModel.includes("/"));
|
|
164284
|
+
const byokAuthorized = !!input.resolvedModel && (input.authorized.has(input.resolvedModel) || !input.resolvedModel.includes("/") || input.resolvedModel.startsWith(`${OPENAI_COMPATIBLE_PROVIDER}/`));
|
|
164144
164285
|
if (input.proxyActive) {
|
|
164145
164286
|
const target = resolveOpenRouterModel(input.model);
|
|
164146
164287
|
if (input.oss) {
|
|
@@ -164949,6 +165090,10 @@ var defaultRunContext = {
|
|
|
164949
165090
|
oss: false,
|
|
164950
165091
|
plan: "none"
|
|
164951
165092
|
};
|
|
165093
|
+
var unknownSecretsRunContext = {
|
|
165094
|
+
...defaultRunContext,
|
|
165095
|
+
secretsUnavailable: true
|
|
165096
|
+
};
|
|
164952
165097
|
async function fetchRunContext(params) {
|
|
164953
165098
|
const timeoutMs = 3e4;
|
|
164954
165099
|
const controller = new AbortController();
|
|
@@ -164967,7 +165112,7 @@ async function fetchRunContext(params) {
|
|
|
164967
165112
|
});
|
|
164968
165113
|
clearTimeout(timeoutId);
|
|
164969
165114
|
if (!response.ok) {
|
|
164970
|
-
return defaultRunContext;
|
|
165115
|
+
return response.status >= 500 ? unknownSecretsRunContext : defaultRunContext;
|
|
164971
165116
|
}
|
|
164972
165117
|
const data = await response.json();
|
|
164973
165118
|
if (data === null) {
|
|
@@ -164991,11 +165136,12 @@ async function fetchRunContext(params) {
|
|
|
164991
165136
|
oss: data.oss ?? false,
|
|
164992
165137
|
plan: data.plan ?? "none",
|
|
164993
165138
|
proxyModel: data.proxyModel,
|
|
164994
|
-
dbSecrets: data.dbSecrets
|
|
165139
|
+
dbSecrets: data.dbSecrets,
|
|
165140
|
+
secretsUnavailable: data.secretsUnavailable
|
|
164995
165141
|
};
|
|
164996
165142
|
} catch {
|
|
164997
165143
|
clearTimeout(timeoutId);
|
|
164998
|
-
return
|
|
165144
|
+
return unknownSecretsRunContext;
|
|
164999
165145
|
}
|
|
165000
165146
|
}
|
|
165001
165147
|
|
|
@@ -165017,7 +165163,10 @@ async function resolveRunContextData(params) {
|
|
|
165017
165163
|
const repoContext = parseRepoContext();
|
|
165018
165164
|
let oidcToken;
|
|
165019
165165
|
try {
|
|
165020
|
-
oidcToken = await core9.getIDToken("pullfrog-api")
|
|
165166
|
+
oidcToken = await op(() => core9.getIDToken("pullfrog-api"), {
|
|
165167
|
+
name: "OIDC mint",
|
|
165168
|
+
retries: process.env.ACTIONS_ID_TOKEN_REQUEST_URL ? [200, 1e3] : []
|
|
165169
|
+
})();
|
|
165021
165170
|
} catch {
|
|
165022
165171
|
}
|
|
165023
165172
|
const [repoResponse, runContext] = await Promise.all([
|
|
@@ -165039,7 +165188,10 @@ async function resolveRunContextData(params) {
|
|
|
165039
165188
|
oss: runContext.oss,
|
|
165040
165189
|
plan: runContext.plan,
|
|
165041
165190
|
proxyModel: runContext.proxyModel,
|
|
165042
|
-
dbSecrets: runContext.dbSecrets
|
|
165191
|
+
dbSecrets: runContext.dbSecrets,
|
|
165192
|
+
// a failed mint on a runner that should have been able to mint is the same
|
|
165193
|
+
// outcome as the server-side failure: the run never sees stored secrets.
|
|
165194
|
+
secretsUnavailable: runContext.secretsUnavailable || !!process.env.ACTIONS_ID_TOKEN_REQUEST_URL && oidcToken === void 0
|
|
165043
165195
|
};
|
|
165044
165196
|
}
|
|
165045
165197
|
|
|
@@ -165204,6 +165356,9 @@ function renderRunError(input) {
|
|
|
165204
165356
|
if (input.errorMessage.includes(MODEL_ACCESS_MARKER)) {
|
|
165205
165357
|
return { summary: input.errorMessage, comment: input.errorMessage };
|
|
165206
165358
|
}
|
|
165359
|
+
if (input.errorMessage.includes(SECRETS_UNAVAILABLE_MARKER)) {
|
|
165360
|
+
return { summary: input.errorMessage, comment: input.errorMessage };
|
|
165361
|
+
}
|
|
165207
165362
|
const isHang = input.errorMessage.startsWith("activity timeout") || input.errorMessage.startsWith("agent still pending");
|
|
165208
165363
|
const hangBody = isHang ? formatAgentHangBody({
|
|
165209
165364
|
diagnostic: input.agentDiagnostic,
|
|
@@ -165938,7 +166093,8 @@ async function main() {
|
|
|
165938
166093
|
model: effectiveModel,
|
|
165939
166094
|
authorized: getAuthorizedModels(),
|
|
165940
166095
|
owner: runContext.repo.owner,
|
|
165941
|
-
name: runContext.repo.name
|
|
166096
|
+
name: runContext.repo.name,
|
|
166097
|
+
secretsUnavailable: runContext.secretsUnavailable
|
|
165942
166098
|
});
|
|
165943
166099
|
}
|
|
165944
166100
|
await setupGit({
|
package/dist/internal.js
CHANGED
|
@@ -381,6 +381,28 @@ var providers = {
|
|
|
381
381
|
}
|
|
382
382
|
}
|
|
383
383
|
}),
|
|
384
|
+
"openai-compatible": provider({
|
|
385
|
+
// "Custom" is the picker group, "OpenAI-compatible" the entry under it, so the
|
|
386
|
+
// menu reads `Custom › OpenAI-compatible` and a second custom backend (a
|
|
387
|
+
// different wire format, say) slots in beside it without a rename. the
|
|
388
|
+
// provider KEY stays `openai-compatible` — it's the stored slug and the
|
|
389
|
+
// `OPENAI_COMPATIBLE_*` env prefix, so this is display-only.
|
|
390
|
+
displayName: "Custom",
|
|
391
|
+
// bring-your-own generic OpenAI-compatible endpoint — Cloudflare AI Gateway,
|
|
392
|
+
// Alibaba DashScope, self-hosted vLLM, or any compatible gateway. base URL +
|
|
393
|
+
// key + model ID are all supplied via env; nothing is cataloged or bumped.
|
|
394
|
+
envVars: ["OPENAI_COMPATIBLE_BASE_URL", "OPENAI_COMPATIBLE_API_KEY", "OPENAI_COMPATIBLE_MODEL"],
|
|
395
|
+
models: {
|
|
396
|
+
// single routing entry — the actual model ID is read from
|
|
397
|
+
// OPENAI_COMPATIBLE_MODEL at run time and the provider is materialized
|
|
398
|
+
// via `@ai-sdk/openai-compatible`.
|
|
399
|
+
byok: {
|
|
400
|
+
displayName: "OpenAI-compatible",
|
|
401
|
+
resolve: "openai-compatible",
|
|
402
|
+
routing: "openai-compatible"
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}),
|
|
384
406
|
openrouter: provider({
|
|
385
407
|
displayName: "OpenRouter",
|
|
386
408
|
envVars: ["OPENROUTER_API_KEY"],
|
package/dist/models.d.ts
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* env var and routes to claude-code for Anthropic IDs or opencode for
|
|
21
21
|
* everything else.
|
|
22
22
|
*/
|
|
23
|
-
export type ModelRouting = "bedrock" | "vertex";
|
|
23
|
+
export type ModelRouting = "bedrock" | "vertex" | "openai-compatible";
|
|
24
24
|
export interface ModelAlias {
|
|
25
25
|
/** stable alias stored in DB, e.g. "anthropic/claude-opus" */
|
|
26
26
|
slug: string;
|
|
@@ -99,6 +99,7 @@ export declare const providers: {
|
|
|
99
99
|
"opencode-go": ProviderConfig;
|
|
100
100
|
bedrock: ProviderConfig;
|
|
101
101
|
vertex: ProviderConfig;
|
|
102
|
+
"openai-compatible": ProviderConfig;
|
|
102
103
|
openrouter: ProviderConfig;
|
|
103
104
|
};
|
|
104
105
|
export type ModelProvider = keyof typeof providers;
|
|
@@ -196,6 +197,31 @@ export declare function getAutoSelectHintModel(): string;
|
|
|
196
197
|
export declare const BEDROCK_MODEL_ID_ENV = "BEDROCK_MODEL_ID";
|
|
197
198
|
/** env var that supplies the Vertex AI model ID for the `vertex/byok` slug. */
|
|
198
199
|
export declare const VERTEX_MODEL_ID_ENV = "VERTEX_MODEL_ID";
|
|
200
|
+
/** provider key + slug prefix for the generic OpenAI-compatible BYOK backend. */
|
|
201
|
+
export declare const OPENAI_COMPATIBLE_PROVIDER = "openai-compatible";
|
|
202
|
+
/** base URL of the user's OpenAI-compatible endpoint (e.g. a Cloudflare AI Gateway URL). */
|
|
203
|
+
export declare const OPENAI_COMPATIBLE_BASE_URL_ENV = "OPENAI_COMPATIBLE_BASE_URL";
|
|
204
|
+
/** API key/token for the user's OpenAI-compatible endpoint — the one sensitive secret. */
|
|
205
|
+
export declare const OPENAI_COMPATIBLE_API_KEY_ENV = "OPENAI_COMPATIBLE_API_KEY";
|
|
206
|
+
/** model ID served by the endpoint, supplied for the `openai-compatible/byok` slug. */
|
|
207
|
+
export declare const OPENAI_COMPATIBLE_MODEL_ENV = "OPENAI_COMPATIBLE_MODEL";
|
|
208
|
+
/**
|
|
209
|
+
* context-window size of the endpoint's model. required — `validateOpenAICompatibleSetup`
|
|
210
|
+
* rejects the run pre-agent when it's unset or non-numeric. it also gates
|
|
211
|
+
* auto-compaction: opencode's `isOverflow` short-circuits when
|
|
212
|
+
* `limit.context === 0`, which would otherwise let a long session grow until the
|
|
213
|
+
* endpoint rejects it on context length.
|
|
214
|
+
*/
|
|
215
|
+
export declare const OPENAI_COMPATIBLE_CONTEXT_ENV = "OPENAI_COMPATIBLE_CONTEXT";
|
|
216
|
+
/**
|
|
217
|
+
* max completion tokens the endpoint's model accepts. required — see
|
|
218
|
+
* OPENAI_COMPATIBLE_CONTEXT_ENV. opencode has no models.dev metadata for a
|
|
219
|
+
* user-supplied endpoint, and an undeclared limit makes it send
|
|
220
|
+
* `max_tokens: 32000`, which most models reject outright (gpt-4o and gpt-4o-mini
|
|
221
|
+
* cap at 16384, many open models at 4096/8192). opencode's `limit` requires
|
|
222
|
+
* `context` + `output` together, so the pair is validated and emitted as a unit.
|
|
223
|
+
*/
|
|
224
|
+
export declare const OPENAI_COMPATIBLE_MAX_OUTPUT_ENV = "OPENAI_COMPATIBLE_MAX_OUTPUT";
|
|
199
225
|
/**
|
|
200
226
|
* the Bedrock model ID passed to claude-code or opencode is whatever the
|
|
201
227
|
* user set in `BEDROCK_MODEL_ID` — Pullfrog never resolves or upgrades it.
|
package/dist/utils/apiKeys.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* marker for the distinct "run-context couldn't hand over your stored secrets"
|
|
3
|
+
* body. surfaced verbatim by `runErrorRenderer` (same contract as
|
|
4
|
+
* `MODEL_ACCESS_MARKER`) because blaming the user for a transient fetch failure
|
|
5
|
+
* on our side is the wrong CTA — their key is configured and still stored.
|
|
6
|
+
*/
|
|
7
|
+
export declare const SECRETS_UNAVAILABLE_MARKER = "couldn't load your Pullfrog secrets";
|
|
1
8
|
/**
|
|
2
9
|
* Validate that the resolved model can actually be served by the chosen
|
|
3
10
|
* agent. For routing slugs (Bedrock / Vertex) the auth shape is multi-var
|
|
@@ -16,6 +23,9 @@ export declare function validateAgentApiKey(params: {
|
|
|
16
23
|
authorized: Set<string>;
|
|
17
24
|
owner: string;
|
|
18
25
|
name: string;
|
|
26
|
+
/** run-context couldn't hand over Pullfrog-stored secrets, so a missing key
|
|
27
|
+
* says nothing about what the user actually configured. */
|
|
28
|
+
secretsUnavailable?: boolean | undefined;
|
|
19
29
|
}): void;
|
|
20
30
|
/**
|
|
21
31
|
* Detect agent-runtime auth failures that should be reformatted as an actionable
|
|
@@ -55,6 +55,13 @@ export interface RunContext {
|
|
|
55
55
|
plan: AccountPlan;
|
|
56
56
|
proxyModel?: string | undefined;
|
|
57
57
|
dbSecrets?: Record<string, string> | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* the server tried and failed to materialize Pullfrog-stored secrets (or we
|
|
60
|
+
* never got a usable response at all). distinct from an absent `dbSecrets`,
|
|
61
|
+
* which legitimately means the user has none stored — without the
|
|
62
|
+
* distinction a transient failure renders as "you have no API key".
|
|
63
|
+
*/
|
|
64
|
+
secretsUnavailable?: boolean | undefined;
|
|
58
65
|
}
|
|
59
66
|
/**
|
|
60
67
|
* fetch run context from Pullfrog API
|
|
@@ -13,6 +13,9 @@ export interface RunContextData {
|
|
|
13
13
|
plan: AccountPlan;
|
|
14
14
|
proxyModel?: string | undefined;
|
|
15
15
|
dbSecrets?: Record<string, string> | undefined;
|
|
16
|
+
/** stored secrets couldn't be materialized for this run — not the same as
|
|
17
|
+
* the user having none. see `RunContext.secretsUnavailable`. */
|
|
18
|
+
secretsUnavailable?: boolean | undefined;
|
|
16
19
|
}
|
|
17
20
|
interface ResolveRunContextDataParams {
|
|
18
21
|
octokit: OctokitWithPlugins;
|