johns-harness 2026.9.14 → 2026.9.16
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/README.md +7 -5
- package/dist/auth-profiles-5CHn7vq1.js +16 -0
- package/dist/compact-B247y5Qt.js +47 -3
- package/dist/config-koj5M_EO.js +16 -0
- package/dist/daemon-cli.js +16 -0
- package/dist/image-generate.js +111 -10
- package/dist/model-selection-BGlGpPgM.js +16 -0
- package/dist/model-selection-BU6wl1le.js +16 -0
- package/dist/model-selection-L7RMwsG-.js +16 -0
- package/dist/pi-embedded-C6ITuRXf.js +47 -3
- package/dist/pi-embedded-DoQsYfIY.js +47 -3
- package/dist/plugin-sdk/config-4Fgm-yEH.js +16 -0
- package/dist/plugin-sdk/config-Di-lyCdh.js +16 -0
- package/dist/plugin-sdk/discord.js +16 -0
- package/dist/plugin-sdk/dispatch-BP0viZiL.js +47 -3
- package/dist/plugin-sdk/dispatch-CQsjmw7g.js +47 -3
- package/dist/plugin-sdk/dispatch-Cerq29sy.js +47 -3
- package/dist/plugin-sdk/dispatch-Cndjtt0g.js +47 -3
- package/dist/plugin-sdk/dispatch-UogiJYul.js +47 -3
- package/dist/plugin-sdk/model-auth-CX9cPHdC.js +16 -0
- package/dist/plugin-sdk/model-auth-Ciehz0x5.js +16 -0
- package/dist/plugin-sdk/model-auth-DVyo6JSX.js +16 -0
- package/dist/plugin-sdk/model-auth-kiHYHGrs.js +16 -0
- package/dist/plugin-sdk/model-selection-Dbj4jDYM.js +16 -0
- package/dist/plugin-sdk/reply-DbZnH8-h.js +47 -3
- package/dist/reply-C5LKjXcC.js +47 -3
- package/docs/PATCHES.md +35 -1
- package/package.json +1 -1
- package/vendor/patched-deps/@mariozechner/pi-ai/dist/models.generated.js +30 -0
package/README.md
CHANGED
|
@@ -55,7 +55,9 @@ Expired ChatGPT credentials trigger an owner notice with the `/login` recovery c
|
|
|
55
55
|
|
|
56
56
|
<!-- JOHNNESS_PATCH_DEFAULTS_61 -->
|
|
57
57
|
### Batteries included
|
|
58
|
-
The harness ships the current model catalog and the short aliases `opus`, `sonnet`, `fable`, `astra`, `sol`, `luna`, `terra`, and `
|
|
58
|
+
The harness ships the current model catalog and the short aliases `opus`, `sonnet`, `fable`, `astra`, `sol`, `luna`, `terra`, `gpt`, `flash`, `banana`, and `image`, so `/model opus` works on a fresh install with no `models.providers` block. Channels you configure load without a `plugins.allow` list; only an explicit `enabled: false` or `plugins.deny` turns one off. Updating the harness updates the catalog and aliases for every agent. Any `openai-codex/<model>` primary that hits its ChatGPT usage limit fails over to `openai/<model>` on the API key first, then to your configured fallbacks, with no config (the sibling is skipped when no OpenAI API key is present). Image generation and editing use GPT Image 2 through the `image_generate` tool: `openai-codex/gpt-image-2` on the ChatGPT subscription first, `openai/gpt-image-2` on the API key second (`agents.defaults.imageGenerationModel`). Nano Banana Pro (`google/gemini-3-pro-image-preview`) ships in the catalog as a selectable image model, reachable with `model: "nano-banana"` and its own `aspectRatio` option; the default stays GPT Image 2 because the subscription path costs nothing.
|
|
59
|
+
|
|
60
|
+
**The alias contract.** Every shipped alias points at the newest model in its line, so an install or an `/update` moves every agent forward with zero per-instance config. Your own `agents.defaults.models[*].alias` entries are still consulted first and still win. Every catalog bump must move the matching alias; the verifier fails the release if it does not.
|
|
59
61
|
|
|
60
62
|
### Updating from chat
|
|
61
63
|
Send `/update` to install the newest `johns-harness` on the machine the gateway runs on and restart into it; `/update 2026.9.14` pins an exact version. Owner only. The reply names the current and target versions, a second `/update` while one is running is refused, and the first message after the restart confirms the running version. On a writable host the package is reinstalled into the existing global prefix. On an immutable image with a persistent volume (Fly), it is installed into `$JOHNNESS_STATE_DIR/runtime/johnness` and the entrypoint prefers that runtime when it is newer than the image: `exec node "$(node /opt/johnness/lib/node_modules/johns-harness/dist/runtime-path.js)" gateway run`. `JOHNNESS_UPDATE_PREFIX` overrides the target. On failure the reply carries the last lines of npm output and the one command that fixes it.
|
|
@@ -72,7 +74,7 @@ node johnness.mjs --help
|
|
|
72
74
|
|
|
73
75
|
```bash
|
|
74
76
|
npm test # 767 tests against the installed dependency tree
|
|
75
|
-
bash scripts/verify-patches.sh #
|
|
77
|
+
bash scripts/verify-patches.sh # 395 checks across every patch family
|
|
76
78
|
```
|
|
77
79
|
|
|
78
80
|
## Launch a swarm with one command
|
|
@@ -118,7 +120,7 @@ Coding agents today run as isolated processes. Two agents on the same repository
|
|
|
118
120
|
- **Memory built for production.** Tiered recall that traces from consolidated reflections down to the verbatim record, surviving compaction and shared across the swarm. Installed and enabled by default.
|
|
119
121
|
- **Production authority per commit.** Promotion is granted to an exact SHA, never to a branch name, so one worker's authorization never covers another worker's commits.
|
|
120
122
|
- **An append-only action log.** Agents record every consequential action to `ACTION_LOG.md` and read it before acting.
|
|
121
|
-
- **A fixed runtime surface.** The core does not grow on a schedule. New mechanisms enter only to solve a demonstrated production failure, one narrow, tested, reversible patch at a time:
|
|
123
|
+
- **A fixed runtime surface.** The core does not grow on a schedule. New mechanisms enter only to solve a demonstrated production failure, one narrow, tested, reversible patch at a time: 64 patch families, each with a regression test and a rollback path.
|
|
122
124
|
- **Reliability with a definition.** Bounded retries, explicit failure states, exact-once continuation, context-pressure recovery, durable delivery, and supervised gateway recovery.
|
|
123
125
|
- **Coding first, phone optional.** Direct the work at a high level from a chat channel while agents plan, implement, test, coordinate, and stage releases unattended.
|
|
124
126
|
- **Local voice.** Optional native Whisper transcription of inbound audio, entirely on your machine. Off by default. [Setup](docs/NATIVE-WHISPER-MUSE.md).
|
|
@@ -185,7 +187,7 @@ The log is a [`RULES.md`](RULES.md) workspace convention enforced through the ac
|
|
|
185
187
|
|
|
186
188
|
## Patch families
|
|
187
189
|
|
|
188
|
-
The runtime carries
|
|
190
|
+
The runtime carries 64 patch families. Each is a production fix applied at the source with a regression test, a durable patch marker, and a rollback path. A verifier runs 395 checks against the package tree and installed dependencies, and 767 tests run against the installed dependency tree.
|
|
189
191
|
|
|
190
192
|
Every family follows the same six steps: reproduce the failure, trace the exact runtime path, make the smallest source-level change that restores the invariant, add a regression test and a patch marker, run the verifier, and retain rollback artifacts. The full index: [`docs/PATCHES.md`](docs/PATCHES.md).
|
|
191
193
|
|
|
@@ -202,7 +204,7 @@ Every family follows the same six steps: reproduce the failure, trace the exact
|
|
|
202
204
|
## Documentation
|
|
203
205
|
|
|
204
206
|
- [`docs/index.md`](docs/index.md): runtime docs, from gateway and channels to plugins, nodes, and CLI reference.
|
|
205
|
-
- [`docs/PATCHES.md`](docs/PATCHES.md): the
|
|
207
|
+
- [`docs/PATCHES.md`](docs/PATCHES.md): the 64 patch families, one record each.
|
|
206
208
|
- [`docs/NATIVE-WHISPER-MUSE.md`](docs/NATIVE-WHISPER-MUSE.md): local Whisper transcription setup.
|
|
207
209
|
- [`RULES.md`](RULES.md): the default operating rules, including Rule Zero on production authorization.
|
|
208
210
|
|
|
@@ -5111,6 +5111,22 @@ const DEFAULT_MODEL_ALIASES = {
|
|
|
5111
5111
|
"gemini-flash": "google/gemini-3-flash-preview",
|
|
5112
5112
|
"gemini-flash-lite": "google/gemini-3.1-flash-lite-preview"
|
|
5113
5113
|
};
|
|
5114
|
+
// JOHNNESS_PATCH_NANO_BANANA_63: the shipped alias contract. Every alias resolves to the newest model
|
|
5115
|
+
// in its line that exists in the shipped catalog, so a fresh install or an /update moves
|
|
5116
|
+
// every agent forward with no per-instance config. This block carries only the aliases
|
|
5117
|
+
// family 63 moves or adds; the rest are already current in the literal above, and later
|
|
5118
|
+
// keys win. A user alias in agents.defaults.models[*].alias is consulted first and still
|
|
5119
|
+
// overrides. Adding a newer model to the catalog without moving its alias fails the
|
|
5120
|
+
// verifier (63.20, scripts/check-alias-contract.mjs).
|
|
5121
|
+
Object.assign(DEFAULT_MODEL_ALIASES, {
|
|
5122
|
+
gpt: "openai-codex/gpt-5.6-sol",
|
|
5123
|
+
flash: "google/gemini-3.8-flash",
|
|
5124
|
+
"gemini-flash": "google/gemini-3.8-flash",
|
|
5125
|
+
"gemini-flash-lite": "google/gemini-2.5-flash-lite",
|
|
5126
|
+
banana: "google/gemini-3-pro-image-preview",
|
|
5127
|
+
"nano-banana": "google/gemini-3-pro-image-preview",
|
|
5128
|
+
image: "openai-codex/gpt-image-2"
|
|
5129
|
+
});
|
|
5114
5130
|
const DEFAULT_MODEL_COST = {
|
|
5115
5131
|
input: 0,
|
|
5116
5132
|
output: 0,
|
package/dist/compact-B247y5Qt.js
CHANGED
|
@@ -127,6 +127,7 @@ import JSON5 from "json5";
|
|
|
127
127
|
import { inspect } from "node:util";
|
|
128
128
|
import fs$1, { appendFile, mkdir } from "node:fs/promises";
|
|
129
129
|
import { complete, completeSimple, createAssistantMessageEventStream, getApiProvider, registerApiProvider, streamSimple } from "@mariozechner/pi-ai";
|
|
130
|
+
import { getModel as getShippedCatalogModel } from "@mariozechner/pi-ai"; // JOHNNESS_PATCH_CODEX_API_SIBLING_FALLBACK_64
|
|
130
131
|
import crypto, { createHash, createHmac, randomBytes, randomUUID } from "node:crypto";
|
|
131
132
|
import { CURRENT_SESSION_VERSION, DefaultResourceLoader, SessionManager, SettingsManager, codingTools, createAgentSession, createEditTool, createReadTool, createWriteTool, estimateTokens, generateSummary, readTool } from "@mariozechner/pi-coding-agent";
|
|
132
133
|
import { EdgeTTS } from "node-edge-tts";
|
|
@@ -18322,7 +18323,12 @@ async function runEmbeddedPiAgent(params) {
|
|
|
18322
18323
|
cfg: params.config,
|
|
18323
18324
|
agentId: params.agentId,
|
|
18324
18325
|
sessionKey: params.sessionKey
|
|
18325
|
-
})
|
|
18326
|
+
}) || hasCodexApiSiblingFallback({
|
|
18327
|
+
provider,
|
|
18328
|
+
model: modelId,
|
|
18329
|
+
cfg: params.config,
|
|
18330
|
+
agentDir
|
|
18331
|
+
}); // JOHNNESS_PATCH_CODEX_API_SIBLING_FALLBACK_64
|
|
18326
18332
|
await ensureJohnnessModelsJson(params.config, agentDir);
|
|
18327
18333
|
let modelResolveOverride;
|
|
18328
18334
|
let legacyBeforeAgentStartResult;
|
|
@@ -46838,6 +46844,34 @@ function resolveImageFallbackCandidates(params) {
|
|
|
46838
46844
|
for (const raw of imageFallbacks) addRaw(raw);
|
|
46839
46845
|
return candidates;
|
|
46840
46846
|
}
|
|
46847
|
+
/* JOHNNESS_PATCH_CODEX_API_SIBLING_FALLBACK_64 (family 64): any openai-codex/<model> primary automatically fails
|
|
46848
|
+
over to openai/<model>, the same model id on the API-key provider, before any configured
|
|
46849
|
+
fallback. The sibling exists only when it is a real entry in the shipped catalog and the
|
|
46850
|
+
openai provider has credentials on this install (auth profile, OPENAI_API_KEY, or
|
|
46851
|
+
models.providers.openai.apiKey); otherwise nothing is added and behavior is unchanged.
|
|
46852
|
+
It is added through addExplicitCandidate, which bypasses the agents.defaults.models
|
|
46853
|
+
allowlist on purpose: the allowlist governs what a user may select, and this candidate is
|
|
46854
|
+
the model the user already selected, reached through a different billing path. */
|
|
46855
|
+
const CODEX_API_SIBLING_PROVIDER = "openai";
|
|
46856
|
+
function resolveCodexApiSiblingCandidate(params) {
|
|
46857
|
+
const provider = normalizeProviderId(String(params.provider ?? ""));
|
|
46858
|
+
const model = String(params.model ?? "").trim();
|
|
46859
|
+
if (provider !== "openai-codex" || !model) return null;
|
|
46860
|
+
try {
|
|
46861
|
+
if (!getShippedCatalogModel(CODEX_API_SIBLING_PROVIDER, model)) return null;
|
|
46862
|
+
const store = params.store ?? ensureAuthProfileStore(params.agentDir, { allowKeychainPrompt: false });
|
|
46863
|
+
if (resolveModelAuthMode(CODEX_API_SIBLING_PROVIDER, params.cfg, store) === "unknown") return null;
|
|
46864
|
+
} catch {
|
|
46865
|
+
return null;
|
|
46866
|
+
}
|
|
46867
|
+
return {
|
|
46868
|
+
provider: CODEX_API_SIBLING_PROVIDER,
|
|
46869
|
+
model
|
|
46870
|
+
};
|
|
46871
|
+
}
|
|
46872
|
+
function hasCodexApiSiblingFallback(params) {
|
|
46873
|
+
return resolveCodexApiSiblingCandidate(params) !== null;
|
|
46874
|
+
}
|
|
46841
46875
|
function resolveFallbackCandidates(params) {
|
|
46842
46876
|
const primary = params.cfg ? resolveConfiguredModelRef({
|
|
46843
46877
|
cfg: params.cfg,
|
|
@@ -46857,6 +46891,14 @@ function resolveFallbackCandidates(params) {
|
|
|
46857
46891
|
defaultProvider
|
|
46858
46892
|
}));
|
|
46859
46893
|
addExplicitCandidate(normalizedPrimary);
|
|
46894
|
+
const codexApiSibling = resolveCodexApiSiblingCandidate({
|
|
46895
|
+
provider: normalizedPrimary.provider,
|
|
46896
|
+
model: normalizedPrimary.model,
|
|
46897
|
+
cfg: params.cfg,
|
|
46898
|
+
agentDir: params.agentDir,
|
|
46899
|
+
store: params.authStore
|
|
46900
|
+
}); // JOHNNESS_PATCH_CODEX_API_SIBLING_FALLBACK_64
|
|
46901
|
+
if (codexApiSibling) addExplicitCandidate(codexApiSibling);
|
|
46860
46902
|
const modelFallbacks = (() => {
|
|
46861
46903
|
if (params.fallbacksOverride !== void 0) return params.fallbacksOverride;
|
|
46862
46904
|
const configuredFallbacks = resolveAgentModelFallbackValues(params.cfg?.agents?.defaults?.model);
|
|
@@ -46932,13 +46974,15 @@ function resolveCooldownDecision(params) {
|
|
|
46932
46974
|
};
|
|
46933
46975
|
}
|
|
46934
46976
|
async function runWithModelFallback(params) {
|
|
46977
|
+
const authStore = params.cfg ? ensureAuthProfileStore(params.agentDir, { allowKeychainPrompt: false }) : null;
|
|
46935
46978
|
const candidates = resolveFallbackCandidates({
|
|
46936
46979
|
cfg: params.cfg,
|
|
46937
46980
|
provider: params.provider,
|
|
46938
46981
|
model: params.model,
|
|
46939
|
-
fallbacksOverride: params.fallbacksOverride
|
|
46982
|
+
fallbacksOverride: params.fallbacksOverride,
|
|
46983
|
+
agentDir: params.agentDir, // JOHNNESS_PATCH_CODEX_API_SIBLING_FALLBACK_64
|
|
46984
|
+
authStore: authStore ?? void 0
|
|
46940
46985
|
});
|
|
46941
|
-
const authStore = params.cfg ? ensureAuthProfileStore(params.agentDir, { allowKeychainPrompt: false }) : null;
|
|
46942
46986
|
const attempts = [];
|
|
46943
46987
|
let lastError;
|
|
46944
46988
|
const hasFallbackCandidates = candidates.length > 1;
|
package/dist/config-koj5M_EO.js
CHANGED
|
@@ -1808,6 +1808,22 @@ const DEFAULT_MODEL_ALIASES = {
|
|
|
1808
1808
|
"gemini-flash": "google/gemini-3-flash-preview",
|
|
1809
1809
|
"gemini-flash-lite": "google/gemini-3.1-flash-lite-preview"
|
|
1810
1810
|
};
|
|
1811
|
+
// JOHNNESS_PATCH_NANO_BANANA_63: the shipped alias contract. Every alias resolves to the newest model
|
|
1812
|
+
// in its line that exists in the shipped catalog, so a fresh install or an /update moves
|
|
1813
|
+
// every agent forward with no per-instance config. This block carries only the aliases
|
|
1814
|
+
// family 63 moves or adds; the rest are already current in the literal above, and later
|
|
1815
|
+
// keys win. A user alias in agents.defaults.models[*].alias is consulted first and still
|
|
1816
|
+
// overrides. Adding a newer model to the catalog without moving its alias fails the
|
|
1817
|
+
// verifier (63.20, scripts/check-alias-contract.mjs).
|
|
1818
|
+
Object.assign(DEFAULT_MODEL_ALIASES, {
|
|
1819
|
+
gpt: "openai-codex/gpt-5.6-sol",
|
|
1820
|
+
flash: "google/gemini-3.8-flash",
|
|
1821
|
+
"gemini-flash": "google/gemini-3.8-flash",
|
|
1822
|
+
"gemini-flash-lite": "google/gemini-2.5-flash-lite",
|
|
1823
|
+
banana: "google/gemini-3-pro-image-preview",
|
|
1824
|
+
"nano-banana": "google/gemini-3-pro-image-preview",
|
|
1825
|
+
image: "openai-codex/gpt-image-2"
|
|
1826
|
+
});
|
|
1811
1827
|
const DEFAULT_MODEL_COST = {
|
|
1812
1828
|
input: 0,
|
|
1813
1829
|
output: 0,
|
package/dist/daemon-cli.js
CHANGED
|
@@ -4959,6 +4959,22 @@ const DEFAULT_MODEL_ALIASES = {
|
|
|
4959
4959
|
"gemini-flash": "google/gemini-3-flash-preview",
|
|
4960
4960
|
"gemini-flash-lite": "google/gemini-3.1-flash-lite-preview"
|
|
4961
4961
|
};
|
|
4962
|
+
// JOHNNESS_PATCH_NANO_BANANA_63: the shipped alias contract. Every alias resolves to the newest model
|
|
4963
|
+
// in its line that exists in the shipped catalog, so a fresh install or an /update moves
|
|
4964
|
+
// every agent forward with no per-instance config. This block carries only the aliases
|
|
4965
|
+
// family 63 moves or adds; the rest are already current in the literal above, and later
|
|
4966
|
+
// keys win. A user alias in agents.defaults.models[*].alias is consulted first and still
|
|
4967
|
+
// overrides. Adding a newer model to the catalog without moving its alias fails the
|
|
4968
|
+
// verifier (63.20, scripts/check-alias-contract.mjs).
|
|
4969
|
+
Object.assign(DEFAULT_MODEL_ALIASES, {
|
|
4970
|
+
gpt: "openai-codex/gpt-5.6-sol",
|
|
4971
|
+
flash: "google/gemini-3.8-flash",
|
|
4972
|
+
"gemini-flash": "google/gemini-3.8-flash",
|
|
4973
|
+
"gemini-flash-lite": "google/gemini-2.5-flash-lite",
|
|
4974
|
+
banana: "google/gemini-3-pro-image-preview",
|
|
4975
|
+
"nano-banana": "google/gemini-3-pro-image-preview",
|
|
4976
|
+
image: "openai-codex/gpt-image-2"
|
|
4977
|
+
});
|
|
4962
4978
|
const DEFAULT_MODEL_COST = {
|
|
4963
4979
|
input: 0,
|
|
4964
4980
|
output: 0,
|
package/dist/image-generate.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// JOHNNESS_PATCH_NANO_BANANA_63
|
|
1
2
|
// JOHNNESS_PATCH_GPT_IMAGE_62
|
|
2
3
|
// First-class `image_generate` agent tool: OpenAI Images API (generations when no input
|
|
3
4
|
// image, edits when one is given) with the model from `agents.defaults.imageGenerationModel`.
|
|
@@ -23,6 +24,35 @@ const SIZES = new Set(["1024x1024", "1536x1024", "1024x1536", "auto"]);
|
|
|
23
24
|
const DEFAULT_MAX_INPUT_BYTES = 20 * 1024 * 1024;
|
|
24
25
|
const JWT_CLAIM_PATH = "https://api.openai.com/auth";
|
|
25
26
|
|
|
27
|
+
// JOHNNESS_PATCH_NANO_BANANA_63: Nano Banana Pro (google/gemini-3-pro-image-preview) is a
|
|
28
|
+
// selectable image model, not a default. The default chain stays subscription first
|
|
29
|
+
// (openai-codex/gpt-image-2, then openai/gpt-image-2); Gemini is API-key billed, so it is
|
|
30
|
+
// reached only through the tool's `model` param or agents.defaults.imageGenerationModel.
|
|
31
|
+
export const GOOGLE_BASE = "https://generativelanguage.googleapis.com/v1beta";
|
|
32
|
+
export const NANO_BANANA_MODEL_ID = "gemini-3-pro-image-preview";
|
|
33
|
+
export const NANO_BANANA_REF = `google/${NANO_BANANA_MODEL_ID}`;
|
|
34
|
+
/** Aspect ratios Nano Banana Pro accepts. Ignored by the OpenAI providers, which use `size`. */
|
|
35
|
+
export const ASPECT_RATIOS = Object.freeze(["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"]);
|
|
36
|
+
/** Bare names the `model` param accepts, resolved to catalog refs before provider routing. */
|
|
37
|
+
export const IMAGE_MODEL_ALIASES = Object.freeze({
|
|
38
|
+
"nano-banana": NANO_BANANA_REF,
|
|
39
|
+
banana: NANO_BANANA_REF,
|
|
40
|
+
"nano-banana-pro": NANO_BANANA_REF,
|
|
41
|
+
"gemini-3-pro-image-preview": NANO_BANANA_REF,
|
|
42
|
+
image: "openai-codex/gpt-image-2",
|
|
43
|
+
"gpt-image-2": "openai-codex/gpt-image-2",
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
/** Resolve an alias or a provider/model ref to a full provider/model ref. */
|
|
47
|
+
export function resolveImageModelRef(raw) {
|
|
48
|
+
const value = String(raw ?? "").trim();
|
|
49
|
+
if (!value) return null;
|
|
50
|
+
const alias = IMAGE_MODEL_ALIASES[value.toLowerCase()];
|
|
51
|
+
if (alias) return alias;
|
|
52
|
+
if (value.includes("/")) return value;
|
|
53
|
+
throw new Error(`Unknown image model "${value}". Use provider/model, or one of: ${Object.keys(IMAGE_MODEL_ALIASES).join(", ")}.`);
|
|
54
|
+
}
|
|
55
|
+
|
|
26
56
|
export function coerceImageGenerationModelConfig(cfg) {
|
|
27
57
|
const raw = cfg?.agents?.defaults?.imageGenerationModel;
|
|
28
58
|
if (typeof raw === "string" && raw.trim()) return { primary: raw.trim(), fallbacks: [] };
|
|
@@ -56,7 +86,15 @@ export function codexAccountIdFromToken(token) {
|
|
|
56
86
|
}
|
|
57
87
|
|
|
58
88
|
/** Endpoint + headers for one provider. Same OAuth transport shape as openai-codex-responses. */
|
|
59
|
-
export function buildProviderRequest({ provider, apiKey, edit, userAgent = "johnness" }) {
|
|
89
|
+
export function buildProviderRequest({ provider, apiKey, edit, userAgent = "johnness", model = NANO_BANANA_MODEL_ID }) {
|
|
90
|
+
if (provider === "google") {
|
|
91
|
+
// JOHNNESS_PATCH_NANO_BANANA_63: Gemini generateContent, API key in x-goog-api-key.
|
|
92
|
+
// One endpoint for both generations and edits; edits inline the inputs as parts.
|
|
93
|
+
return {
|
|
94
|
+
url: `${GOOGLE_BASE}/models/${model}:generateContent`,
|
|
95
|
+
headers: { "x-goog-api-key": apiKey, "content-type": "application/json", "User-Agent": userAgent },
|
|
96
|
+
};
|
|
97
|
+
}
|
|
60
98
|
if (provider === "openai-codex") {
|
|
61
99
|
const accountId = codexAccountIdFromToken(apiKey);
|
|
62
100
|
if (!accountId) throw new ProviderUnsupported("codex token has no chatgpt_account_id");
|
|
@@ -92,10 +130,14 @@ function sanitizeName(input, fallback) {
|
|
|
92
130
|
return base || fallback;
|
|
93
131
|
}
|
|
94
132
|
|
|
95
|
-
|
|
133
|
+
// JOHNNESS_PATCH_NANO_BANANA_63: `ext` defaults to png, which is what the OpenAI Images API
|
|
134
|
+
// returns and what family 62 shipped. Nano Banana Pro answers with image/jpeg, so the caller
|
|
135
|
+
// passes the extension the returned bytes actually are rather than mislabeling a JPEG .png.
|
|
136
|
+
export function resolveOutputPath({ workspaceDir, output, index = 0, count = 1, ext = "png" }) {
|
|
96
137
|
const root = path.resolve(workspaceDir);
|
|
97
138
|
const mediaDir = path.join(root, "media", "generated");
|
|
98
139
|
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
140
|
+
const suffix = String(ext || "png").replace(/^\.+/, "").toLowerCase().replace(/[^a-z0-9]/g, "") || "png";
|
|
99
141
|
let target;
|
|
100
142
|
if (output && String(output).trim()) {
|
|
101
143
|
const raw = String(output).trim();
|
|
@@ -103,13 +145,21 @@ export function resolveOutputPath({ workspaceDir, output, index = 0, count = 1 }
|
|
|
103
145
|
const resolved = path.isAbsolute(raw) ? path.resolve(raw) : path.resolve(root, raw);
|
|
104
146
|
const rel = path.relative(root, resolved);
|
|
105
147
|
if (!rel || rel.startsWith("..") || path.isAbsolute(rel)) throw new Error(`output must stay inside the workspace: ${raw}`);
|
|
106
|
-
target = count > 1 ? path.join(path.dirname(resolved), `${sanitizeName(path.basename(resolved), "image")}-${index + 1}
|
|
148
|
+
target = count > 1 ? path.join(path.dirname(resolved), `${sanitizeName(path.basename(resolved), "image")}-${index + 1}.${suffix}`) : resolved.replace(/\.[a-z0-9]+$/i, "") + "." + suffix;
|
|
107
149
|
} else {
|
|
108
|
-
target = path.join(mediaDir, `gpt-image-${stamp}${count > 1 ? `-${index + 1}` : ""}
|
|
150
|
+
target = path.join(mediaDir, `gpt-image-${stamp}${count > 1 ? `-${index + 1}` : ""}.${suffix}`);
|
|
109
151
|
}
|
|
110
152
|
return target;
|
|
111
153
|
}
|
|
112
154
|
|
|
155
|
+
/** File extension for the bytes a provider returned. JOHNNESS_PATCH_NANO_BANANA_63 */
|
|
156
|
+
export function extensionForMimeType(mimeType) {
|
|
157
|
+
const value = String(mimeType ?? "").toLowerCase();
|
|
158
|
+
if (value.includes("jpeg") || value.includes("jpg")) return "jpg";
|
|
159
|
+
if (value.includes("webp")) return "webp";
|
|
160
|
+
return "png";
|
|
161
|
+
}
|
|
162
|
+
|
|
113
163
|
export function validateParams(args) {
|
|
114
164
|
const record = args && typeof args === "object" ? args : {};
|
|
115
165
|
const prompt = typeof record.prompt === "string" ? record.prompt.trim() : "";
|
|
@@ -125,12 +175,58 @@ export function validateParams(args) {
|
|
|
125
175
|
const countRaw = typeof record.count === "number" ? Math.floor(record.count) : 1;
|
|
126
176
|
if (!Number.isFinite(countRaw) || countRaw < 1 || countRaw > 4) throw new Error("count must be 1..4");
|
|
127
177
|
const quality = typeof record.quality === "string" && ["low", "medium", "high", "auto"].includes(record.quality) ? record.quality : void 0;
|
|
128
|
-
|
|
178
|
+
// JOHNNESS_PATCH_NANO_BANANA_63: aliases resolve to catalog refs; aspectRatio is Gemini only.
|
|
179
|
+
const model = typeof record.model === "string" && record.model.trim() ? resolveImageModelRef(record.model) : void 0;
|
|
180
|
+
const aspectRatio = typeof record.aspectRatio === "string" && record.aspectRatio.trim() ? record.aspectRatio.trim() : void 0;
|
|
181
|
+
if (aspectRatio && !ASPECT_RATIOS.includes(aspectRatio)) throw new Error(`aspectRatio must be one of ${ASPECT_RATIOS.join(", ")}`);
|
|
182
|
+
return { prompt, inputs, mask, size, count: countRaw, quality, aspectRatio, output: typeof record.output === "string" ? record.output : void 0, model };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* JOHNNESS_PATCH_NANO_BANANA_63: Gemini generateContent branch.
|
|
187
|
+
* Generations send the prompt alone; edits send the prompt plus one inlineData part per input
|
|
188
|
+
* image. `aspectRatio` rides generationConfig.imageConfig. Failure classification is shared with
|
|
189
|
+
* the OpenAI branches (isUnsupportedStatus) so fallback chaining behaves identically.
|
|
190
|
+
*/
|
|
191
|
+
async function callGoogleProvider({ candidate, apiKey, params, images, fetchImpl, userAgent }) {
|
|
192
|
+
const edit = images.length > 0;
|
|
193
|
+
const { url, headers } = buildProviderRequest({ provider: candidate.provider, apiKey, edit, userAgent, model: candidate.model });
|
|
194
|
+
const parts = [{ text: params.prompt }];
|
|
195
|
+
for (const img of images) parts.push({ inlineData: { mimeType: img.mimeType, data: Buffer.from(img.buffer).toString("base64") } });
|
|
196
|
+
const generationConfig = { responseModalities: ["IMAGE"] };
|
|
197
|
+
if (params.aspectRatio) generationConfig.imageConfig = { aspectRatio: params.aspectRatio };
|
|
198
|
+
const res = await fetchImpl(url, { method: "POST", headers, body: JSON.stringify({ contents: [{ role: "user", parts }], generationConfig }) });
|
|
199
|
+
const text = await res.text();
|
|
200
|
+
if (!res.ok) {
|
|
201
|
+
if (isUnsupportedStatus(res.status, text)) throw new ProviderUnsupported(`${candidate.provider} ${res.status}: ${redact(text)}`, res.status);
|
|
202
|
+
throw new Error(`${candidate.provider} ${res.status}: ${redact(text)}`);
|
|
203
|
+
}
|
|
204
|
+
let json;
|
|
205
|
+
try {
|
|
206
|
+
json = JSON.parse(text);
|
|
207
|
+
} catch {
|
|
208
|
+
throw new Error(`${candidate.provider}: non-JSON response`);
|
|
209
|
+
}
|
|
210
|
+
const buffers = [];
|
|
211
|
+
let mimeType = "image/png";
|
|
212
|
+
for (const candidateOut of Array.isArray(json?.candidates) ? json.candidates : []) {
|
|
213
|
+
for (const part of Array.isArray(candidateOut?.content?.parts) ? candidateOut.content.parts : []) {
|
|
214
|
+
const inline = part?.inlineData ?? part?.inline_data;
|
|
215
|
+
const data = inline?.data;
|
|
216
|
+
if (typeof data === "string" && data) {
|
|
217
|
+
buffers.push(Buffer.from(data, "base64"));
|
|
218
|
+
if (inline?.mimeType || inline?.mime_type) mimeType = inline.mimeType ?? inline.mime_type;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (buffers.length === 0) throw new Error(`${candidate.provider}: response carried no image data`);
|
|
223
|
+
return { buffers, usage: json?.usageMetadata ?? null, mode: edit ? "edit" : "generate", mimeType };
|
|
129
224
|
}
|
|
130
225
|
|
|
131
226
|
async function callProvider({ candidate, apiKey, params, images, mask, fetchImpl, userAgent }) {
|
|
227
|
+
if (candidate.provider === "google") return callGoogleProvider({ candidate, apiKey, params, images, fetchImpl, userAgent }); // JOHNNESS_PATCH_NANO_BANANA_63
|
|
132
228
|
const edit = images.length > 0;
|
|
133
|
-
const { url, headers } = buildProviderRequest({ provider: candidate.provider, apiKey, edit, userAgent });
|
|
229
|
+
const { url, headers } = buildProviderRequest({ provider: candidate.provider, apiKey, edit, userAgent, model: candidate.model });
|
|
134
230
|
let body;
|
|
135
231
|
if (edit) {
|
|
136
232
|
body = new FormData();
|
|
@@ -168,7 +264,7 @@ async function callProvider({ candidate, apiKey, params, images, mask, fetchImpl
|
|
|
168
264
|
}
|
|
169
265
|
}
|
|
170
266
|
if (buffers.length === 0) throw new Error(`${candidate.provider}: response carried no image data`);
|
|
171
|
-
return { buffers, usage: json?.usage ?? null, mode: edit ? "edit" : "generate" };
|
|
267
|
+
return { buffers, usage: json?.usage ?? null, mode: edit ? "edit" : "generate", mimeType: "image/png" };
|
|
172
268
|
}
|
|
173
269
|
|
|
174
270
|
/**
|
|
@@ -217,7 +313,7 @@ export function createImageGenerateTool(options, deps) {
|
|
|
217
313
|
return {
|
|
218
314
|
label: "Image Generate",
|
|
219
315
|
name: "image_generate",
|
|
220
|
-
description: "Generate or edit images with GPT Image 2 (default openai-codex/gpt-image-2, then openai/gpt-image-2). Give a prompt; add image (or images, up to 4) to edit, and an optional mask PNG. Writes PNG files under the workspace media/generated folder and returns their paths; send them with the message tool.",
|
|
316
|
+
description: "Generate or edit images with GPT Image 2 (default openai-codex/gpt-image-2, then openai/gpt-image-2) or Nano Banana Pro (model: nano-banana). Give a prompt; add image (or images, up to 4) to edit, and an optional mask PNG. Writes PNG files under the workspace media/generated folder and returns their paths; send them with the message tool.",
|
|
221
317
|
parameters: Type.Object({
|
|
222
318
|
prompt: Type.String({ description: "What to draw or how to change the input image." }),
|
|
223
319
|
image: Type.Optional(Type.String({ description: "Input image path to edit (workspace or allowed local root)." })),
|
|
@@ -227,7 +323,8 @@ export function createImageGenerateTool(options, deps) {
|
|
|
227
323
|
count: Type.Optional(Type.Number({ description: "Number of images, 1..4 (default 1)." })),
|
|
228
324
|
quality: Type.Optional(Type.String({ description: "low, medium, high, or auto." })),
|
|
229
325
|
output: Type.Optional(Type.String({ description: "Output path inside the workspace (default media/generated/gpt-image-<timestamp>.png)." })),
|
|
230
|
-
model: Type.Optional(Type.String({ description: "Override provider/model, for example openai/gpt-image-2." })),
|
|
326
|
+
model: Type.Optional(Type.String({ description: "Override provider/model, for example openai/gpt-image-2, or an alias: nano-banana (Nano Banana Pro), banana, image." })),
|
|
327
|
+
aspectRatio: Type.Optional(Type.String({ description: "Nano Banana Pro only: 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, or 21:9. Ignored by the GPT Image 2 providers." })),
|
|
231
328
|
}),
|
|
232
329
|
execute: async (_toolCallId, args) => {
|
|
233
330
|
const params = validateParams(args);
|
|
@@ -242,7 +339,9 @@ export function createImageGenerateTool(options, deps) {
|
|
|
242
339
|
const images = [];
|
|
243
340
|
for (const ref of params.inputs) images.push(await load(ref));
|
|
244
341
|
const mask = params.mask ? await load(params.mask) : void 0;
|
|
245
|
-
|
|
342
|
+
// Validate the output path before spending a call, then resolve it again once the
|
|
343
|
+
// provider has told us what the bytes actually are. JOHNNESS_PATCH_NANO_BANANA_63
|
|
344
|
+
resolveOutputPath({ workspaceDir, output: params.output, index: 0, count: params.count });
|
|
246
345
|
const result = await generateWithFallback({
|
|
247
346
|
cfg: options?.config,
|
|
248
347
|
params,
|
|
@@ -252,6 +351,8 @@ export function createImageGenerateTool(options, deps) {
|
|
|
252
351
|
userAgent: deps.userAgent,
|
|
253
352
|
resolveAuth: ({ provider }) => deps.resolveApiKeyForProvider({ provider, cfg: options?.config, agentDir }),
|
|
254
353
|
});
|
|
354
|
+
const ext = extensionForMimeType(result.mimeType);
|
|
355
|
+
const outputs = Array.from({ length: params.count }, (_, i) => resolveOutputPath({ workspaceDir, output: params.output, index: i, count: params.count, ext }));
|
|
255
356
|
const written = [];
|
|
256
357
|
for (let i = 0; i < result.buffers.length && i < outputs.length; i++) {
|
|
257
358
|
await fs.mkdir(path.dirname(outputs[i]), { recursive: true });
|
|
@@ -1665,6 +1665,22 @@ const DEFAULT_MODEL_ALIASES = {
|
|
|
1665
1665
|
"gemini-flash": "google/gemini-3-flash-preview",
|
|
1666
1666
|
"gemini-flash-lite": "google/gemini-3.1-flash-lite-preview"
|
|
1667
1667
|
};
|
|
1668
|
+
// JOHNNESS_PATCH_NANO_BANANA_63: the shipped alias contract. Every alias resolves to the newest model
|
|
1669
|
+
// in its line that exists in the shipped catalog, so a fresh install or an /update moves
|
|
1670
|
+
// every agent forward with no per-instance config. This block carries only the aliases
|
|
1671
|
+
// family 63 moves or adds; the rest are already current in the literal above, and later
|
|
1672
|
+
// keys win. A user alias in agents.defaults.models[*].alias is consulted first and still
|
|
1673
|
+
// overrides. Adding a newer model to the catalog without moving its alias fails the
|
|
1674
|
+
// verifier (63.20, scripts/check-alias-contract.mjs).
|
|
1675
|
+
Object.assign(DEFAULT_MODEL_ALIASES, {
|
|
1676
|
+
gpt: "openai-codex/gpt-5.6-sol",
|
|
1677
|
+
flash: "google/gemini-3.8-flash",
|
|
1678
|
+
"gemini-flash": "google/gemini-3.8-flash",
|
|
1679
|
+
"gemini-flash-lite": "google/gemini-2.5-flash-lite",
|
|
1680
|
+
banana: "google/gemini-3-pro-image-preview",
|
|
1681
|
+
"nano-banana": "google/gemini-3-pro-image-preview",
|
|
1682
|
+
image: "openai-codex/gpt-image-2"
|
|
1683
|
+
});
|
|
1668
1684
|
const DEFAULT_MODEL_COST = {
|
|
1669
1685
|
input: 0,
|
|
1670
1686
|
output: 0,
|
|
@@ -1640,6 +1640,22 @@ const DEFAULT_MODEL_ALIASES = {
|
|
|
1640
1640
|
"gemini-flash": "google/gemini-3-flash-preview",
|
|
1641
1641
|
"gemini-flash-lite": "google/gemini-3.1-flash-lite-preview"
|
|
1642
1642
|
};
|
|
1643
|
+
// JOHNNESS_PATCH_NANO_BANANA_63: the shipped alias contract. Every alias resolves to the newest model
|
|
1644
|
+
// in its line that exists in the shipped catalog, so a fresh install or an /update moves
|
|
1645
|
+
// every agent forward with no per-instance config. This block carries only the aliases
|
|
1646
|
+
// family 63 moves or adds; the rest are already current in the literal above, and later
|
|
1647
|
+
// keys win. A user alias in agents.defaults.models[*].alias is consulted first and still
|
|
1648
|
+
// overrides. Adding a newer model to the catalog without moving its alias fails the
|
|
1649
|
+
// verifier (63.20, scripts/check-alias-contract.mjs).
|
|
1650
|
+
Object.assign(DEFAULT_MODEL_ALIASES, {
|
|
1651
|
+
gpt: "openai-codex/gpt-5.6-sol",
|
|
1652
|
+
flash: "google/gemini-3.8-flash",
|
|
1653
|
+
"gemini-flash": "google/gemini-3.8-flash",
|
|
1654
|
+
"gemini-flash-lite": "google/gemini-2.5-flash-lite",
|
|
1655
|
+
banana: "google/gemini-3-pro-image-preview",
|
|
1656
|
+
"nano-banana": "google/gemini-3-pro-image-preview",
|
|
1657
|
+
image: "openai-codex/gpt-image-2"
|
|
1658
|
+
});
|
|
1643
1659
|
const DEFAULT_MODEL_COST = {
|
|
1644
1660
|
input: 0,
|
|
1645
1661
|
output: 0,
|
|
@@ -1571,6 +1571,22 @@ const DEFAULT_MODEL_ALIASES = {
|
|
|
1571
1571
|
"gemini-flash": "google/gemini-3-flash-preview",
|
|
1572
1572
|
"gemini-flash-lite": "google/gemini-3.1-flash-lite-preview"
|
|
1573
1573
|
};
|
|
1574
|
+
// JOHNNESS_PATCH_NANO_BANANA_63: the shipped alias contract. Every alias resolves to the newest model
|
|
1575
|
+
// in its line that exists in the shipped catalog, so a fresh install or an /update moves
|
|
1576
|
+
// every agent forward with no per-instance config. This block carries only the aliases
|
|
1577
|
+
// family 63 moves or adds; the rest are already current in the literal above, and later
|
|
1578
|
+
// keys win. A user alias in agents.defaults.models[*].alias is consulted first and still
|
|
1579
|
+
// overrides. Adding a newer model to the catalog without moving its alias fails the
|
|
1580
|
+
// verifier (63.20, scripts/check-alias-contract.mjs).
|
|
1581
|
+
Object.assign(DEFAULT_MODEL_ALIASES, {
|
|
1582
|
+
gpt: "openai-codex/gpt-5.6-sol",
|
|
1583
|
+
flash: "google/gemini-3.8-flash",
|
|
1584
|
+
"gemini-flash": "google/gemini-3.8-flash",
|
|
1585
|
+
"gemini-flash-lite": "google/gemini-2.5-flash-lite",
|
|
1586
|
+
banana: "google/gemini-3-pro-image-preview",
|
|
1587
|
+
"nano-banana": "google/gemini-3-pro-image-preview",
|
|
1588
|
+
image: "openai-codex/gpt-image-2"
|
|
1589
|
+
});
|
|
1574
1590
|
const DEFAULT_MODEL_COST = {
|
|
1575
1591
|
input: 0,
|
|
1576
1592
|
output: 0,
|
|
@@ -73,6 +73,7 @@ import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
|
|
|
73
73
|
import { fileURLToPath } from "node:url";
|
|
74
74
|
import crypto, { X509Certificate, createHash, createHmac, randomBytes, randomUUID } from "node:crypto";
|
|
75
75
|
import { complete, completeSimple, createAssistantMessageEventStream, getApiProvider, registerApiProvider, streamSimple } from "@mariozechner/pi-ai";
|
|
76
|
+
import { getModel as getShippedCatalogModel } from "@mariozechner/pi-ai"; // JOHNNESS_PATCH_CODEX_API_SIBLING_FALLBACK_64
|
|
76
77
|
import { CURRENT_SESSION_VERSION, DefaultResourceLoader, SessionManager, SettingsManager, codingTools, createAgentSession, createEditTool, createReadTool, createWriteTool, estimateTokens, generateSummary, readTool } from "@mariozechner/pi-coding-agent";
|
|
77
78
|
import { EdgeTTS } from "node-edge-tts";
|
|
78
79
|
import * as net$1 from "node:net";
|
|
@@ -39963,6 +39964,34 @@ function resolveImageFallbackCandidates(params) {
|
|
|
39963
39964
|
for (const raw of imageFallbacks) addRaw(raw);
|
|
39964
39965
|
return candidates;
|
|
39965
39966
|
}
|
|
39967
|
+
/* JOHNNESS_PATCH_CODEX_API_SIBLING_FALLBACK_64 (family 64): any openai-codex/<model> primary automatically fails
|
|
39968
|
+
over to openai/<model>, the same model id on the API-key provider, before any configured
|
|
39969
|
+
fallback. The sibling exists only when it is a real entry in the shipped catalog and the
|
|
39970
|
+
openai provider has credentials on this install (auth profile, OPENAI_API_KEY, or
|
|
39971
|
+
models.providers.openai.apiKey); otherwise nothing is added and behavior is unchanged.
|
|
39972
|
+
It is added through addExplicitCandidate, which bypasses the agents.defaults.models
|
|
39973
|
+
allowlist on purpose: the allowlist governs what a user may select, and this candidate is
|
|
39974
|
+
the model the user already selected, reached through a different billing path. */
|
|
39975
|
+
const CODEX_API_SIBLING_PROVIDER = "openai";
|
|
39976
|
+
function resolveCodexApiSiblingCandidate(params) {
|
|
39977
|
+
const provider = normalizeProviderId(String(params.provider ?? ""));
|
|
39978
|
+
const model = String(params.model ?? "").trim();
|
|
39979
|
+
if (provider !== "openai-codex" || !model) return null;
|
|
39980
|
+
try {
|
|
39981
|
+
if (!getShippedCatalogModel(CODEX_API_SIBLING_PROVIDER, model)) return null;
|
|
39982
|
+
const store = params.store ?? ensureAuthProfileStore(params.agentDir, { allowKeychainPrompt: false });
|
|
39983
|
+
if (resolveModelAuthMode(CODEX_API_SIBLING_PROVIDER, params.cfg, store) === "unknown") return null;
|
|
39984
|
+
} catch {
|
|
39985
|
+
return null;
|
|
39986
|
+
}
|
|
39987
|
+
return {
|
|
39988
|
+
provider: CODEX_API_SIBLING_PROVIDER,
|
|
39989
|
+
model
|
|
39990
|
+
};
|
|
39991
|
+
}
|
|
39992
|
+
function hasCodexApiSiblingFallback(params) {
|
|
39993
|
+
return resolveCodexApiSiblingCandidate(params) !== null;
|
|
39994
|
+
}
|
|
39966
39995
|
function resolveFallbackCandidates(params) {
|
|
39967
39996
|
const primary = params.cfg ? resolveConfiguredModelRef({
|
|
39968
39997
|
cfg: params.cfg,
|
|
@@ -39982,6 +40011,14 @@ function resolveFallbackCandidates(params) {
|
|
|
39982
40011
|
defaultProvider
|
|
39983
40012
|
}));
|
|
39984
40013
|
addExplicitCandidate(normalizedPrimary);
|
|
40014
|
+
const codexApiSibling = resolveCodexApiSiblingCandidate({
|
|
40015
|
+
provider: normalizedPrimary.provider,
|
|
40016
|
+
model: normalizedPrimary.model,
|
|
40017
|
+
cfg: params.cfg,
|
|
40018
|
+
agentDir: params.agentDir,
|
|
40019
|
+
store: params.authStore
|
|
40020
|
+
}); // JOHNNESS_PATCH_CODEX_API_SIBLING_FALLBACK_64
|
|
40021
|
+
if (codexApiSibling) addExplicitCandidate(codexApiSibling);
|
|
39985
40022
|
const modelFallbacks = (() => {
|
|
39986
40023
|
if (params.fallbacksOverride !== void 0) return params.fallbacksOverride;
|
|
39987
40024
|
const configuredFallbacks = resolveAgentModelFallbackValues(params.cfg?.agents?.defaults?.model);
|
|
@@ -40057,13 +40094,15 @@ function resolveCooldownDecision(params) {
|
|
|
40057
40094
|
};
|
|
40058
40095
|
}
|
|
40059
40096
|
async function runWithModelFallback(params) {
|
|
40097
|
+
const authStore = params.cfg ? ensureAuthProfileStore(params.agentDir, { allowKeychainPrompt: false }) : null;
|
|
40060
40098
|
const candidates = resolveFallbackCandidates({
|
|
40061
40099
|
cfg: params.cfg,
|
|
40062
40100
|
provider: params.provider,
|
|
40063
40101
|
model: params.model,
|
|
40064
|
-
fallbacksOverride: params.fallbacksOverride
|
|
40102
|
+
fallbacksOverride: params.fallbacksOverride,
|
|
40103
|
+
agentDir: params.agentDir, // JOHNNESS_PATCH_CODEX_API_SIBLING_FALLBACK_64
|
|
40104
|
+
authStore: authStore ?? void 0
|
|
40065
40105
|
});
|
|
40066
|
-
const authStore = params.cfg ? ensureAuthProfileStore(params.agentDir, { allowKeychainPrompt: false }) : null;
|
|
40067
40106
|
const attempts = [];
|
|
40068
40107
|
let lastError;
|
|
40069
40108
|
const hasFallbackCandidates = candidates.length > 1;
|
|
@@ -103926,7 +103965,12 @@ async function runEmbeddedPiAgent(params) {
|
|
|
103926
103965
|
cfg: params.config,
|
|
103927
103966
|
agentId: params.agentId,
|
|
103928
103967
|
sessionKey: params.sessionKey
|
|
103929
|
-
})
|
|
103968
|
+
}) || hasCodexApiSiblingFallback({
|
|
103969
|
+
provider,
|
|
103970
|
+
model: modelId,
|
|
103971
|
+
cfg: params.config,
|
|
103972
|
+
agentDir
|
|
103973
|
+
}); // JOHNNESS_PATCH_CODEX_API_SIBLING_FALLBACK_64
|
|
103930
103974
|
await ensureJohnnessModelsJson(params.config, agentDir);
|
|
103931
103975
|
let modelResolveOverride;
|
|
103932
103976
|
let legacyBeforeAgentStartResult;
|