@mrciphersmith/keryx 0.3.3 → 0.3.6
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.js +996 -366
- package/docs/README.md +2 -0
- package/package.json +1 -1
- package/src/gdskills/bundled/install-manifest.json +520 -48
- package/src/gdskills/bundled/stacks/csharp-dotnet/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/governance/eval.json +1881 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/governance/scout.json +33 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/pack.json +38 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/rules/coding-style.mdc +100 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/rules/patterns.mdc +107 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/rules/security.mdc +86 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/rules/testing.mdc +89 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-build-fix/SKILL.md +143 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-build-fix/evals.json +77 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-code-review/SKILL.md +121 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-code-review/evals.json +77 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-implementation/SKILL.md +134 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-implementation/evals.json +76 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-testing/SKILL.md +130 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-testing/evals.json +77 -0
- package/src/gdskills/bundled/stacks/django/agent-refs.json +3 -0
- package/src/gdskills/bundled/stacks/django/governance/eval.json +1763 -0
- package/src/gdskills/bundled/stacks/django/governance/scout.json +40 -0
- package/src/gdskills/bundled/stacks/django/pack.json +43 -0
- package/src/gdskills/bundled/stacks/django/rules/coding-style.mdc +80 -0
- package/src/gdskills/bundled/stacks/django/rules/patterns.mdc +92 -0
- package/src/gdskills/bundled/stacks/django/rules/security.mdc +92 -0
- package/src/gdskills/bundled/stacks/django/rules/testing.mdc +89 -0
- package/src/gdskills/bundled/stacks/django/skills/django-build-fix/SKILL.md +149 -0
- package/src/gdskills/bundled/stacks/django/skills/django-build-fix/evals.json +49 -0
- package/src/gdskills/bundled/stacks/django/skills/django-code-review/SKILL.md +137 -0
- package/src/gdskills/bundled/stacks/django/skills/django-code-review/evals.json +48 -0
- package/src/gdskills/bundled/stacks/django/skills/django-implementation/SKILL.md +147 -0
- package/src/gdskills/bundled/stacks/django/skills/django-implementation/evals.json +75 -0
- package/src/gdskills/bundled/stacks/django/skills/django-migrate/SKILL.md +166 -0
- package/src/gdskills/bundled/stacks/django/skills/django-migrate/evals.json +49 -0
- package/src/gdskills/bundled/stacks/django/skills/django-testing/SKILL.md +130 -0
- package/src/gdskills/bundled/stacks/django/skills/django-testing/evals.json +48 -0
- package/src/gdskills/bundled/stacks/fastapi/agent-refs.json +3 -0
- package/src/gdskills/bundled/stacks/fastapi/governance/eval.json +1777 -0
- package/src/gdskills/bundled/stacks/fastapi/governance/scout.json +34 -0
- package/src/gdskills/bundled/stacks/fastapi/pack.json +43 -0
- package/src/gdskills/bundled/stacks/fastapi/rules/coding-style.mdc +68 -0
- package/src/gdskills/bundled/stacks/fastapi/rules/patterns.mdc +108 -0
- package/src/gdskills/bundled/stacks/fastapi/rules/security.mdc +99 -0
- package/src/gdskills/bundled/stacks/fastapi/rules/testing.mdc +85 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-build-fix/SKILL.md +157 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-build-fix/evals.json +76 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-code-review/SKILL.md +150 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-code-review/evals.json +74 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-implementation/SKILL.md +158 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-implementation/evals.json +75 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-testing/SKILL.md +146 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-testing/evals.json +74 -0
- package/src/gdskills/bundled/stacks/flutter-dart/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/flutter-dart/governance/eval.json +1849 -0
- package/src/gdskills/bundled/stacks/flutter-dart/governance/scout.json +33 -0
- package/src/gdskills/bundled/stacks/flutter-dart/pack.json +41 -0
- package/src/gdskills/bundled/stacks/flutter-dart/rules/coding-style.mdc +98 -0
- package/src/gdskills/bundled/stacks/flutter-dart/rules/patterns.mdc +88 -0
- package/src/gdskills/bundled/stacks/flutter-dart/rules/security.mdc +91 -0
- package/src/gdskills/bundled/stacks/flutter-dart/rules/testing.mdc +101 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-build-fix/SKILL.md +134 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-build-fix/evals.json +79 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-code-review/SKILL.md +124 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-code-review/evals.json +74 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-implementation/SKILL.md +139 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-implementation/evals.json +77 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-testing/SKILL.md +134 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-testing/evals.json +74 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/agent-refs.json +3 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/governance/eval.json +2194 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/governance/scout.json +39 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/pack.json +40 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/rules/coding-style.mdc +67 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/rules/patterns.mdc +65 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/rules/security.mdc +69 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/rules/testing.mdc +80 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-build-fix/SKILL.md +144 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-build-fix/evals.json +74 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-code-review/SKILL.md +129 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-code-review/evals.json +74 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-implementation/SKILL.md +147 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-implementation/evals.json +75 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-migrate/SKILL.md +139 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-migrate/evals.json +74 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-testing/SKILL.md +128 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-testing/evals.json +73 -0
- package/src/gdskills/bundled/stacks/kotlin-android/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/kotlin-android/governance/eval.json +1889 -0
- package/src/gdskills/bundled/stacks/kotlin-android/governance/scout.json +34 -0
- package/src/gdskills/bundled/stacks/kotlin-android/pack.json +38 -0
- package/src/gdskills/bundled/stacks/kotlin-android/rules/coding-style.mdc +89 -0
- package/src/gdskills/bundled/stacks/kotlin-android/rules/patterns.mdc +96 -0
- package/src/gdskills/bundled/stacks/kotlin-android/rules/security.mdc +90 -0
- package/src/gdskills/bundled/stacks/kotlin-android/rules/testing.mdc +89 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/compose-implementation/SKILL.md +150 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/compose-implementation/evals.json +77 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-build-fix/SKILL.md +151 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-build-fix/evals.json +76 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-code-review/SKILL.md +139 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-code-review/evals.json +78 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-testing/SKILL.md +131 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-testing/evals.json +77 -0
- package/src/gdskills/bundled/stacks/python/agent-refs.json +2 -1
- package/src/gdskills/bundled/stacks/python/pack.json +1 -1
- package/src/gdskills/bundled/stacks/rust/agent-refs.json +3 -0
- package/src/gdskills/bundled/stacks/rust/governance/eval.json +1823 -0
- package/src/gdskills/bundled/stacks/rust/governance/scout.json +32 -0
- package/src/gdskills/bundled/stacks/rust/pack.json +42 -0
- package/src/gdskills/bundled/stacks/rust/rules/coding-style.mdc +93 -0
- package/src/gdskills/bundled/stacks/rust/rules/patterns.mdc +85 -0
- package/src/gdskills/bundled/stacks/rust/rules/security.mdc +85 -0
- package/src/gdskills/bundled/stacks/rust/rules/testing.mdc +82 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-build-fix/SKILL.md +141 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-build-fix/evals.json +78 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-code-review/SKILL.md +127 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-code-review/evals.json +72 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-implementation/SKILL.md +133 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-implementation/evals.json +79 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-testing/SKILL.md +130 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-testing/evals.json +75 -0
- package/src/gdskills/bundled/stacks/swift-ios/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/swift-ios/governance/eval.json +1803 -0
- package/src/gdskills/bundled/stacks/swift-ios/governance/scout.json +32 -0
- package/src/gdskills/bundled/stacks/swift-ios/pack.json +38 -0
- package/src/gdskills/bundled/stacks/swift-ios/rules/coding-style.mdc +92 -0
- package/src/gdskills/bundled/stacks/swift-ios/rules/patterns.mdc +112 -0
- package/src/gdskills/bundled/stacks/swift-ios/rules/security.mdc +78 -0
- package/src/gdskills/bundled/stacks/swift-ios/rules/testing.mdc +90 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-build-fix/SKILL.md +144 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-build-fix/evals.json +75 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-code-review/SKILL.md +122 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-code-review/evals.json +75 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-testing/SKILL.md +131 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-testing/evals.json +75 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swiftui-implementation/SKILL.md +149 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swiftui-implementation/evals.json +76 -0
- package/src/gdskills/bundled/agents/python-build-fixer.md +0 -52
- package/src/gdskills/bundled/agents/python-code-auditor.md +0 -49
package/dist/cli.js
CHANGED
|
@@ -690,7 +690,7 @@ function deviceCodeMethodLabel(provider) {
|
|
|
690
690
|
return "SuperGrok Subscription";
|
|
691
691
|
if (provider === "github-copilot")
|
|
692
692
|
return "Login with GitHub Copilot";
|
|
693
|
-
if (provider === "openai")
|
|
693
|
+
if (provider === "openai-codex")
|
|
694
694
|
return "ChatGPT Plus/Pro (headless)";
|
|
695
695
|
return "Device authorization";
|
|
696
696
|
}
|
|
@@ -728,8 +728,13 @@ var init_catalog = __esm(() => {
|
|
|
728
728
|
},
|
|
729
729
|
{
|
|
730
730
|
provider: "openai",
|
|
731
|
-
label: "OpenAI",
|
|
732
|
-
methods: ["
|
|
731
|
+
label: "OpenAI API",
|
|
732
|
+
methods: ["api-key"]
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
provider: "openai-codex",
|
|
736
|
+
label: "ChatGPT / Codex subscription",
|
|
737
|
+
methods: ["device-code"]
|
|
733
738
|
},
|
|
734
739
|
{
|
|
735
740
|
provider: "github-copilot",
|
|
@@ -1991,19 +1996,47 @@ var init_model_profile = __esm(() => {
|
|
|
1991
1996
|
STRENGTH_TIERS = new Set(["light", "standard", "deep"]);
|
|
1992
1997
|
});
|
|
1993
1998
|
|
|
1999
|
+
// src/lib/oauth/openai-token.ts
|
|
2000
|
+
function codexTokenMetadata(token) {
|
|
2001
|
+
if (typeof token !== "string" || token.length > 1e5 || token.split(".").length !== 3)
|
|
2002
|
+
return {};
|
|
2003
|
+
try {
|
|
2004
|
+
const payload = JSON.parse(Buffer.from(token.split(".")[1], "base64url").toString("utf8"));
|
|
2005
|
+
if (typeof payload !== "object" || payload === null)
|
|
2006
|
+
return {};
|
|
2007
|
+
const claims = payload;
|
|
2008
|
+
const auth = claims["https://api.openai.com/auth"];
|
|
2009
|
+
const accountId = typeof auth === "object" && auth !== null ? auth.chatgpt_account_id : undefined;
|
|
2010
|
+
return {
|
|
2011
|
+
...typeof accountId === "string" && accountId.length > 0 && accountId.trim() === accountId && !/[\r\n]/.test(accountId) ? { accountId } : {},
|
|
2012
|
+
...typeof claims.exp === "number" && Number.isFinite(claims.exp) && claims.exp > 0 ? { expiresAt: claims.exp * 1000 } : {}
|
|
2013
|
+
};
|
|
2014
|
+
} catch {
|
|
2015
|
+
return {};
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
|
|
1994
2019
|
// src/lib/oauth/grants.ts
|
|
1995
2020
|
function loadOAuthGrant(provider, dir) {
|
|
1996
2021
|
const grants = loadShellConfig(dir).oauthGrants;
|
|
1997
2022
|
if (grants === undefined) {
|
|
1998
2023
|
return;
|
|
1999
2024
|
}
|
|
2000
|
-
const grant = grants[provider];
|
|
2025
|
+
const grant = grants[provider] ?? (provider === "openai-codex" ? grants.openai : undefined);
|
|
2001
2026
|
if (grant === undefined || typeof grant.access !== "string" || grant.access.length === 0) {
|
|
2002
2027
|
return;
|
|
2003
2028
|
}
|
|
2004
2029
|
if (grant.method !== "device-code" && grant.method !== "oauth-pkce-loopback") {
|
|
2005
2030
|
return;
|
|
2006
2031
|
}
|
|
2032
|
+
if (provider === "openai-codex" || provider === "openai") {
|
|
2033
|
+
const metadata = codexTokenMetadata(grant.access);
|
|
2034
|
+
return {
|
|
2035
|
+
...grant,
|
|
2036
|
+
...grant.accountId === undefined && metadata.accountId !== undefined ? { accountId: metadata.accountId } : {},
|
|
2037
|
+
...grant.expires === undefined && metadata.expiresAt !== undefined ? { expires: metadata.expiresAt } : {}
|
|
2038
|
+
};
|
|
2039
|
+
}
|
|
2007
2040
|
return grant;
|
|
2008
2041
|
}
|
|
2009
2042
|
function saveOAuthGrant(provider, grant, dir) {
|
|
@@ -2012,15 +2045,17 @@ function saveOAuthGrant(provider, grant, dir) {
|
|
|
2012
2045
|
}
|
|
2013
2046
|
function deleteOAuthGrant(provider, dir) {
|
|
2014
2047
|
const existing = { ...loadShellConfig(dir).oauthGrants ?? {} };
|
|
2015
|
-
if (existing[provider] === undefined) {
|
|
2048
|
+
if (existing[provider] === undefined && !(provider === "openai-codex" && existing.openai !== undefined)) {
|
|
2016
2049
|
return;
|
|
2017
2050
|
}
|
|
2018
2051
|
delete existing[provider];
|
|
2052
|
+
if (provider === "openai-codex")
|
|
2053
|
+
delete existing.openai;
|
|
2019
2054
|
saveShellConfig({ oauthGrants: existing }, dir);
|
|
2020
2055
|
}
|
|
2021
2056
|
function listOAuthGrantProviders(dir) {
|
|
2022
2057
|
const grants = loadShellConfig(dir).oauthGrants ?? {};
|
|
2023
|
-
return Object.keys(grants).filter((name) => loadOAuthGrant(name, dir) !== undefined).sort();
|
|
2058
|
+
return [...new Set(Object.keys(grants).map((name) => name === "openai" ? "openai-codex" : name))].filter((name) => loadOAuthGrant(name, dir) !== undefined).sort();
|
|
2024
2059
|
}
|
|
2025
2060
|
function oauthGrantStatus(provider, dir, now = Date.now) {
|
|
2026
2061
|
const grant = loadOAuthGrant(provider, dir);
|
|
@@ -2087,6 +2122,8 @@ function grantFromTokens(method, tokens, now = Date.now) {
|
|
|
2087
2122
|
access: tokens.accessToken,
|
|
2088
2123
|
...tokens.refreshToken !== undefined ? { refresh: tokens.refreshToken } : {},
|
|
2089
2124
|
...tokens.expiresInSeconds !== undefined ? { expires: now() + tokens.expiresInSeconds * 1000 } : {},
|
|
2125
|
+
...tokens.accountId !== undefined ? { accountId: tokens.accountId } : {},
|
|
2126
|
+
...tokens.expiresInSeconds === undefined && tokens.expiresAt !== undefined ? { expires: tokens.expiresAt } : {},
|
|
2090
2127
|
obtainedAt
|
|
2091
2128
|
};
|
|
2092
2129
|
}
|
|
@@ -2265,6 +2302,315 @@ var init_device_code = __esm(() => {
|
|
|
2265
2302
|
};
|
|
2266
2303
|
});
|
|
2267
2304
|
|
|
2305
|
+
// src/lib/oauth/openai-codex.ts
|
|
2306
|
+
function withSignal(base, signal) {
|
|
2307
|
+
if (signal?.aborted === true)
|
|
2308
|
+
throw new DeviceCodeError("cancelled", "device authorization was cancelled");
|
|
2309
|
+
const timeout = AbortSignal.timeout(30000);
|
|
2310
|
+
return { ...base, signal: signal === undefined ? timeout : AbortSignal.any([signal, timeout]) };
|
|
2311
|
+
}
|
|
2312
|
+
async function requestCodexDeviceCode(http) {
|
|
2313
|
+
const response = await http.fetch(`${OPENAI_CODEX_ISSUER}/api/accounts/deviceauth/usercode`, withSignal({
|
|
2314
|
+
method: "POST",
|
|
2315
|
+
headers: { "Content-Type": "application/json", Accept: "application/json", "User-Agent": "keryx" },
|
|
2316
|
+
body: JSON.stringify({ client_id: OPENAI_CODEX_CLIENT_ID })
|
|
2317
|
+
}, http.signal));
|
|
2318
|
+
const json = await response.json().catch(() => ({}));
|
|
2319
|
+
if (!response.ok || typeof json !== "object" || json === null) {
|
|
2320
|
+
throw new DeviceCodeError("failed", `ChatGPT device authorization failed (HTTP ${response.status})`);
|
|
2321
|
+
}
|
|
2322
|
+
const rec = json;
|
|
2323
|
+
rec.user_code ??= rec.usercode;
|
|
2324
|
+
if (typeof rec.device_auth_id !== "string" || rec.device_auth_id.length === 0 || typeof rec.user_code !== "string" || rec.user_code.length === 0 || [...rec.user_code].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127)) {
|
|
2325
|
+
throw new DeviceCodeError("failed", "ChatGPT device authorization is missing device_auth_id / user_code");
|
|
2326
|
+
}
|
|
2327
|
+
const intervalSeconds = Number(rec.interval);
|
|
2328
|
+
return {
|
|
2329
|
+
deviceAuthId: rec.device_auth_id,
|
|
2330
|
+
userCode: rec.user_code,
|
|
2331
|
+
verificationUri: `${OPENAI_CODEX_ISSUER}/codex/device`,
|
|
2332
|
+
intervalMs: Number.isFinite(intervalSeconds) && intervalSeconds > 0 ? intervalSeconds * 1000 : 5000
|
|
2333
|
+
};
|
|
2334
|
+
}
|
|
2335
|
+
async function pollCodexDeviceToken(challenge, http) {
|
|
2336
|
+
const sleep = http.sleep;
|
|
2337
|
+
const now = http.now ?? Date.now;
|
|
2338
|
+
const deadline = now() + DEVICE_LOGIN_TIMEOUT_MS;
|
|
2339
|
+
while (now() < deadline) {
|
|
2340
|
+
if (http.signal?.aborted === true) {
|
|
2341
|
+
throw new DeviceCodeError("cancelled", "device authorization was cancelled");
|
|
2342
|
+
}
|
|
2343
|
+
const response = await http.fetch(`${OPENAI_CODEX_ISSUER}/api/accounts/deviceauth/token`, withSignal({
|
|
2344
|
+
method: "POST",
|
|
2345
|
+
headers: { "Content-Type": "application/json", Accept: "application/json", "User-Agent": "keryx" },
|
|
2346
|
+
body: JSON.stringify({ device_auth_id: challenge.deviceAuthId, user_code: challenge.userCode })
|
|
2347
|
+
}, http.signal));
|
|
2348
|
+
if (response.ok) {
|
|
2349
|
+
const data = await response.json();
|
|
2350
|
+
if (typeof data.authorization_code !== "string" || typeof data.code_verifier !== "string") {
|
|
2351
|
+
throw new DeviceCodeError("failed", "ChatGPT device token is missing authorization_code");
|
|
2352
|
+
}
|
|
2353
|
+
return exchangeCodexAuthorizationCode(data.authorization_code, data.code_verifier, http);
|
|
2354
|
+
}
|
|
2355
|
+
if (response.status !== 403 && response.status !== 404) {
|
|
2356
|
+
throw new DeviceCodeError("failed", `ChatGPT device token poll failed (HTTP ${response.status})`);
|
|
2357
|
+
}
|
|
2358
|
+
await abortableSleep(Math.min(challenge.intervalMs, Math.max(0, deadline - now())), sleep, http.signal);
|
|
2359
|
+
}
|
|
2360
|
+
throw new DeviceCodeError("timeout", "ChatGPT device authorization timed out");
|
|
2361
|
+
}
|
|
2362
|
+
async function exchangeCodexAuthorizationCode(code, codeVerifier, http) {
|
|
2363
|
+
const response = await http.fetch(`${OPENAI_CODEX_ISSUER}/oauth/token`, withSignal({
|
|
2364
|
+
method: "POST",
|
|
2365
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
|
|
2366
|
+
body: new URLSearchParams({
|
|
2367
|
+
grant_type: "authorization_code",
|
|
2368
|
+
code,
|
|
2369
|
+
redirect_uri: `${OPENAI_CODEX_ISSUER}/deviceauth/callback`,
|
|
2370
|
+
client_id: OPENAI_CODEX_CLIENT_ID,
|
|
2371
|
+
code_verifier: codeVerifier
|
|
2372
|
+
}).toString()
|
|
2373
|
+
}, http.signal));
|
|
2374
|
+
const json = await response.json().catch(() => ({}));
|
|
2375
|
+
if (!response.ok || typeof json !== "object" || json === null || typeof json.access_token !== "string") {
|
|
2376
|
+
throw new DeviceCodeError("failed", `ChatGPT token exchange failed (HTTP ${response.status})`);
|
|
2377
|
+
}
|
|
2378
|
+
const rec = json;
|
|
2379
|
+
const access = codexTokenMetadata(rec.access_token);
|
|
2380
|
+
const accountId = codexTokenMetadata(rec.id_token).accountId ?? access.accountId;
|
|
2381
|
+
return {
|
|
2382
|
+
accessToken: rec.access_token,
|
|
2383
|
+
...accountId !== undefined ? { accountId } : {},
|
|
2384
|
+
...access.expiresAt !== undefined ? { expiresAt: access.expiresAt } : {},
|
|
2385
|
+
...typeof rec.refresh_token === "string" ? { refreshToken: rec.refresh_token } : {},
|
|
2386
|
+
...typeof rec.expires_in === "number" && Number.isFinite(rec.expires_in) && rec.expires_in > 0 ? { expiresInSeconds: rec.expires_in } : {}
|
|
2387
|
+
};
|
|
2388
|
+
}
|
|
2389
|
+
async function abortableSleep(ms, sleep, signal) {
|
|
2390
|
+
if (signal?.aborted === true)
|
|
2391
|
+
throw new DeviceCodeError("cancelled", "device authorization was cancelled");
|
|
2392
|
+
await new Promise((resolve, reject) => {
|
|
2393
|
+
let timer;
|
|
2394
|
+
const cleanup = () => {
|
|
2395
|
+
if (timer !== undefined)
|
|
2396
|
+
clearTimeout(timer);
|
|
2397
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2398
|
+
};
|
|
2399
|
+
const onAbort = () => {
|
|
2400
|
+
cleanup();
|
|
2401
|
+
reject(new DeviceCodeError("cancelled", "device authorization was cancelled"));
|
|
2402
|
+
};
|
|
2403
|
+
const done = () => {
|
|
2404
|
+
cleanup();
|
|
2405
|
+
resolve();
|
|
2406
|
+
};
|
|
2407
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2408
|
+
if (sleep === undefined)
|
|
2409
|
+
timer = setTimeout(done, ms);
|
|
2410
|
+
else
|
|
2411
|
+
sleep(ms).then(done, () => {
|
|
2412
|
+
cleanup();
|
|
2413
|
+
reject(new DeviceCodeError("failed", "device authorization wait failed"));
|
|
2414
|
+
});
|
|
2415
|
+
});
|
|
2416
|
+
}
|
|
2417
|
+
var OPENAI_CODEX_ISSUER = "https://auth.openai.com", OPENAI_CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann", DEVICE_LOGIN_TIMEOUT_MS;
|
|
2418
|
+
var init_openai_codex = __esm(() => {
|
|
2419
|
+
init_device_code();
|
|
2420
|
+
DEVICE_LOGIN_TIMEOUT_MS = 15 * 60 * 1000;
|
|
2421
|
+
});
|
|
2422
|
+
|
|
2423
|
+
// src/lib/oauth/openai-subscription.ts
|
|
2424
|
+
import { resolve, join } from "path";
|
|
2425
|
+
function failure(detail) {
|
|
2426
|
+
return new SubscriptionAuthError(`ChatGPT subscription: ${detail}. ${LOGIN}`);
|
|
2427
|
+
}
|
|
2428
|
+
function enrich(grant) {
|
|
2429
|
+
const metadata = codexTokenMetadata(grant.access);
|
|
2430
|
+
return {
|
|
2431
|
+
...grant,
|
|
2432
|
+
...grant.accountId === undefined && metadata.accountId !== undefined ? { accountId: metadata.accountId } : {},
|
|
2433
|
+
...grant.expires === undefined && metadata.expiresAt !== undefined ? { expires: metadata.expiresAt } : {}
|
|
2434
|
+
};
|
|
2435
|
+
}
|
|
2436
|
+
function cancelled(signal) {
|
|
2437
|
+
if (signal?.aborted)
|
|
2438
|
+
throw failure("authorization cancelled");
|
|
2439
|
+
}
|
|
2440
|
+
async function refreshOpenAiCodexGrant(input) {
|
|
2441
|
+
cancelled(input.signal);
|
|
2442
|
+
const loaded = loadOAuthGrant("openai-codex", input.configDir);
|
|
2443
|
+
if (loaded === undefined)
|
|
2444
|
+
throw failure("not signed in");
|
|
2445
|
+
const grant = enrich(loaded);
|
|
2446
|
+
const now = input.now ?? Date.now;
|
|
2447
|
+
if (!input.forceRefresh && grant.expires !== undefined && grant.expires > now() + 120000)
|
|
2448
|
+
return grant;
|
|
2449
|
+
if (!grant.refresh) {
|
|
2450
|
+
if (input.forceRefresh || grant.expires !== undefined && grant.expires <= now())
|
|
2451
|
+
throw failure("login expired and cannot be refreshed");
|
|
2452
|
+
return grant;
|
|
2453
|
+
}
|
|
2454
|
+
const key = resolve(input.configDir ?? keryxConfigDir());
|
|
2455
|
+
let pending = refreshes.get(key);
|
|
2456
|
+
if (pending === undefined) {
|
|
2457
|
+
pending = refreshWithLock(grant, input, key);
|
|
2458
|
+
refreshes.set(key, pending);
|
|
2459
|
+
pending.finally(() => {
|
|
2460
|
+
if (refreshes.get(key) === pending)
|
|
2461
|
+
refreshes.delete(key);
|
|
2462
|
+
}).catch(() => {});
|
|
2463
|
+
}
|
|
2464
|
+
if (input.signal === undefined)
|
|
2465
|
+
return pending;
|
|
2466
|
+
return new Promise((resolveGrant, reject) => {
|
|
2467
|
+
const abort = () => {
|
|
2468
|
+
cleanup();
|
|
2469
|
+
reject(failure("authorization cancelled"));
|
|
2470
|
+
};
|
|
2471
|
+
const cleanup = () => input.signal?.removeEventListener("abort", abort);
|
|
2472
|
+
input.signal?.addEventListener("abort", abort, { once: true });
|
|
2473
|
+
if (input.signal?.aborted) {
|
|
2474
|
+
abort();
|
|
2475
|
+
return;
|
|
2476
|
+
}
|
|
2477
|
+
pending.then((result) => {
|
|
2478
|
+
cleanup();
|
|
2479
|
+
resolveGrant(result);
|
|
2480
|
+
}, (error) => {
|
|
2481
|
+
cleanup();
|
|
2482
|
+
reject(error);
|
|
2483
|
+
});
|
|
2484
|
+
});
|
|
2485
|
+
}
|
|
2486
|
+
async function refreshWithLock(initial, input, configDir) {
|
|
2487
|
+
try {
|
|
2488
|
+
return await withFileLock(join(configDir, ".openai-codex-refresh.lock"), async () => {
|
|
2489
|
+
const current = loadOAuthGrant("openai-codex", input.configDir);
|
|
2490
|
+
if (current === undefined || current.obtainedAt !== initial.obtainedAt || initial.accountId !== undefined && current.accountId !== initial.accountId) {
|
|
2491
|
+
throw failure("login changed while waiting for token refresh");
|
|
2492
|
+
}
|
|
2493
|
+
if (current.access !== initial.access || current.refresh !== initial.refresh)
|
|
2494
|
+
return enrich(current);
|
|
2495
|
+
return refreshGrant(current, input);
|
|
2496
|
+
}, { timeoutMs: 35000, retryMs: 50, staleMs: 30000, heartbeatMs: 1000 });
|
|
2497
|
+
} catch (error) {
|
|
2498
|
+
if (error instanceof SubscriptionAuthError)
|
|
2499
|
+
throw error;
|
|
2500
|
+
throw failure("could not acquire or release the token refresh lock");
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
async function refreshGrant(grant, input) {
|
|
2504
|
+
const now = input.now ?? Date.now;
|
|
2505
|
+
try {
|
|
2506
|
+
const response = await input.fetch(`${OPENAI_CODEX_ISSUER}/oauth/token`, {
|
|
2507
|
+
method: "POST",
|
|
2508
|
+
signal: AbortSignal.timeout(30000),
|
|
2509
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
2510
|
+
body: JSON.stringify({ grant_type: "refresh_token", client_id: OPENAI_CODEX_CLIENT_ID, refresh_token: grant.refresh })
|
|
2511
|
+
});
|
|
2512
|
+
if (!response.ok)
|
|
2513
|
+
throw failure(`token refresh failed (HTTP ${response.status})`);
|
|
2514
|
+
const json = await response.json();
|
|
2515
|
+
if (typeof json !== "object" || json === null)
|
|
2516
|
+
throw failure("invalid token refresh response");
|
|
2517
|
+
const rec = json;
|
|
2518
|
+
if (typeof rec.access_token !== "string" || rec.access_token.length === 0)
|
|
2519
|
+
throw failure("token refresh returned no access token");
|
|
2520
|
+
const id = codexTokenMetadata(rec.id_token);
|
|
2521
|
+
const access = codexTokenMetadata(rec.access_token);
|
|
2522
|
+
const expires = typeof rec.expires_in === "number" && Number.isFinite(rec.expires_in) && rec.expires_in > 0 ? now() + rec.expires_in * 1000 : access.expiresAt;
|
|
2523
|
+
const accountId = id.accountId ?? access.accountId ?? grant.accountId;
|
|
2524
|
+
if (grant.accountId !== undefined && accountId !== grant.accountId)
|
|
2525
|
+
throw failure("refreshed account does not match saved login");
|
|
2526
|
+
const next = {
|
|
2527
|
+
...grant,
|
|
2528
|
+
access: rec.access_token,
|
|
2529
|
+
...typeof rec.refresh_token === "string" && rec.refresh_token.length > 0 ? { refresh: rec.refresh_token } : {},
|
|
2530
|
+
...accountId !== undefined ? { accountId } : {},
|
|
2531
|
+
lastRefreshedAt: new Date(now()).toISOString()
|
|
2532
|
+
};
|
|
2533
|
+
if (expires !== undefined)
|
|
2534
|
+
next.expires = expires;
|
|
2535
|
+
else
|
|
2536
|
+
delete next.expires;
|
|
2537
|
+
const current = loadOAuthGrant("openai-codex", input.configDir);
|
|
2538
|
+
if (current?.access !== grant.access || current.refresh !== grant.refresh)
|
|
2539
|
+
throw failure("login changed during token refresh");
|
|
2540
|
+
saveOAuthGrant("openai-codex", next, input.configDir);
|
|
2541
|
+
const persisted = loadOAuthGrant("openai-codex", input.configDir);
|
|
2542
|
+
if (persisted?.access !== next.access || persisted.refresh !== next.refresh)
|
|
2543
|
+
throw failure("could not save refreshed login");
|
|
2544
|
+
return next;
|
|
2545
|
+
} catch (error) {
|
|
2546
|
+
if (error instanceof SubscriptionAuthError)
|
|
2547
|
+
throw error;
|
|
2548
|
+
throw failure("token refresh failed");
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
async function ensureOpenAiCodexGrant(input) {
|
|
2552
|
+
const grant = await refreshOpenAiCodexGrant(input);
|
|
2553
|
+
cancelled(input.signal);
|
|
2554
|
+
if (grant.accountId === undefined || grant.accountId.length === 0 || /[\r\n]/.test(grant.accountId))
|
|
2555
|
+
throw failure("saved login has no account identity");
|
|
2556
|
+
if (grant.expires !== undefined && grant.expires <= (input.now ?? Date.now)())
|
|
2557
|
+
throw failure("login has expired");
|
|
2558
|
+
return { ...grant, accountId: grant.accountId };
|
|
2559
|
+
}
|
|
2560
|
+
var refreshes, LOGIN = "Run `keryx auth login openai-codex`.", SubscriptionAuthError;
|
|
2561
|
+
var init_openai_subscription = __esm(() => {
|
|
2562
|
+
init_config_dir();
|
|
2563
|
+
init_fs();
|
|
2564
|
+
init_grants();
|
|
2565
|
+
init_openai_codex();
|
|
2566
|
+
refreshes = new Map;
|
|
2567
|
+
SubscriptionAuthError = class SubscriptionAuthError extends Error {
|
|
2568
|
+
};
|
|
2569
|
+
});
|
|
2570
|
+
|
|
2571
|
+
// src/commands/subscription-models.ts
|
|
2572
|
+
async function fetchOpenAiCodexModels(fetchFn, opts = {}) {
|
|
2573
|
+
const controller = new AbortController;
|
|
2574
|
+
const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 8000);
|
|
2575
|
+
let authenticated = false;
|
|
2576
|
+
try {
|
|
2577
|
+
const grant = await ensureOpenAiCodexGrant({ fetch: fetchFn, signal: controller.signal, ...opts.configDir !== undefined ? { configDir: opts.configDir } : {} });
|
|
2578
|
+
authenticated = true;
|
|
2579
|
+
const response = await fetchFn(`${OPENAI_CODEX_PICKER.baseUrl}/models?client_version=${CODEX_CATALOG_CLIENT_VERSION}`, {
|
|
2580
|
+
headers: { Authorization: `Bearer ${grant.access}`, "ChatGPT-Account-ID": grant.accountId, originator: "keryx", Accept: "application/json" },
|
|
2581
|
+
signal: controller.signal,
|
|
2582
|
+
redirect: "error"
|
|
2583
|
+
});
|
|
2584
|
+
if (!response.ok) {
|
|
2585
|
+
return { models: [], source: "fallback", failure: { kind: response.status === 401 || response.status === 403 ? "rejected" : "http", status: response.status } };
|
|
2586
|
+
}
|
|
2587
|
+
const body = await response.json();
|
|
2588
|
+
const entries = typeof body === "object" && body !== null && "models" in body && Array.isArray(body.models) ? body.models : [];
|
|
2589
|
+
const models = [...new Set(entries.flatMap((entry) => {
|
|
2590
|
+
if (typeof entry !== "object" || entry === null || !("slug" in entry) || typeof entry.slug !== "string" || entry.slug.length === 0)
|
|
2591
|
+
return [];
|
|
2592
|
+
if (!("visibility" in entry) || entry.visibility !== "list")
|
|
2593
|
+
return [];
|
|
2594
|
+
return [entry.slug];
|
|
2595
|
+
}))];
|
|
2596
|
+
return models.length > 0 ? { models, source: "live" } : { models: [], source: "fallback", failure: { kind: "empty" } };
|
|
2597
|
+
} catch {
|
|
2598
|
+
return { models: [], source: "fallback", failure: controller.signal.aborted || authenticated ? { kind: "unreachable", detail: controller.signal.aborted ? "model listing timed out" : "subscription model listing failed" } : { kind: "rejected", status: 401, detail: "Run `keryx auth login openai-codex` to connect your ChatGPT subscription." } };
|
|
2599
|
+
} finally {
|
|
2600
|
+
clearTimeout(timer);
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
var OPENAI_CODEX_PICKER, CODEX_CATALOG_CLIENT_VERSION = "0.157.0";
|
|
2604
|
+
var init_subscription_models = __esm(() => {
|
|
2605
|
+
init_openai_subscription();
|
|
2606
|
+
OPENAI_CODEX_PICKER = {
|
|
2607
|
+
name: "openai-codex",
|
|
2608
|
+
label: "ChatGPT / Codex",
|
|
2609
|
+
baseUrl: "https://chatgpt.com/backend-api/codex",
|
|
2610
|
+
models: ["gpt-5.3-codex"]
|
|
2611
|
+
};
|
|
2612
|
+
});
|
|
2613
|
+
|
|
2268
2614
|
// src/lib/oauth/copilot.ts
|
|
2269
2615
|
function copilotApiBaseFromExchange(raw) {
|
|
2270
2616
|
if (typeof raw !== "string" || raw.length === 0) {
|
|
@@ -2357,87 +2703,6 @@ var init_copilot = __esm(() => {
|
|
|
2357
2703
|
init_device_code();
|
|
2358
2704
|
});
|
|
2359
2705
|
|
|
2360
|
-
// src/lib/oauth/openai-codex.ts
|
|
2361
|
-
function withSignal(base, signal) {
|
|
2362
|
-
return signal === undefined ? base : { ...base, signal };
|
|
2363
|
-
}
|
|
2364
|
-
async function requestCodexDeviceCode(http) {
|
|
2365
|
-
const response = await http.fetch(`${OPENAI_CODEX_ISSUER}/api/accounts/deviceauth/usercode`, withSignal({
|
|
2366
|
-
method: "POST",
|
|
2367
|
-
headers: { "Content-Type": "application/json", Accept: "application/json", "User-Agent": "keryx" },
|
|
2368
|
-
body: JSON.stringify({ client_id: OPENAI_CODEX_CLIENT_ID })
|
|
2369
|
-
}, http.signal));
|
|
2370
|
-
const json = await response.json().catch(() => ({}));
|
|
2371
|
-
if (!response.ok || typeof json !== "object" || json === null) {
|
|
2372
|
-
throw new DeviceCodeError("failed", `ChatGPT device authorization failed (HTTP ${response.status})`);
|
|
2373
|
-
}
|
|
2374
|
-
const rec = json;
|
|
2375
|
-
if (typeof rec.device_auth_id !== "string" || typeof rec.user_code !== "string") {
|
|
2376
|
-
throw new DeviceCodeError("failed", "ChatGPT device authorization is missing device_auth_id / user_code");
|
|
2377
|
-
}
|
|
2378
|
-
const intervalSeconds = Number(rec.interval);
|
|
2379
|
-
return {
|
|
2380
|
-
deviceAuthId: rec.device_auth_id,
|
|
2381
|
-
userCode: rec.user_code,
|
|
2382
|
-
verificationUri: `${OPENAI_CODEX_ISSUER}/codex/device`,
|
|
2383
|
-
intervalMs: Number.isFinite(intervalSeconds) && intervalSeconds > 0 ? intervalSeconds * 1000 : 5000
|
|
2384
|
-
};
|
|
2385
|
-
}
|
|
2386
|
-
async function pollCodexDeviceToken(challenge, http) {
|
|
2387
|
-
const sleep = http.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
2388
|
-
const now = http.now ?? Date.now;
|
|
2389
|
-
const deadline = now() + 5 * 60 * 1000;
|
|
2390
|
-
while (now() < deadline) {
|
|
2391
|
-
if (http.signal?.aborted === true) {
|
|
2392
|
-
throw new DeviceCodeError("cancelled", "device authorization was cancelled");
|
|
2393
|
-
}
|
|
2394
|
-
const response = await http.fetch(`${OPENAI_CODEX_ISSUER}/api/accounts/deviceauth/token`, withSignal({
|
|
2395
|
-
method: "POST",
|
|
2396
|
-
headers: { "Content-Type": "application/json", Accept: "application/json", "User-Agent": "keryx" },
|
|
2397
|
-
body: JSON.stringify({ device_auth_id: challenge.deviceAuthId, user_code: challenge.userCode })
|
|
2398
|
-
}, http.signal));
|
|
2399
|
-
if (response.ok) {
|
|
2400
|
-
const data = await response.json();
|
|
2401
|
-
if (typeof data.authorization_code !== "string" || typeof data.code_verifier !== "string") {
|
|
2402
|
-
throw new DeviceCodeError("failed", "ChatGPT device token is missing authorization_code");
|
|
2403
|
-
}
|
|
2404
|
-
return exchangeCodexAuthorizationCode(data.authorization_code, data.code_verifier, http);
|
|
2405
|
-
}
|
|
2406
|
-
if (response.status !== 403 && response.status !== 404) {
|
|
2407
|
-
throw new DeviceCodeError("failed", `ChatGPT device token poll failed (HTTP ${response.status})`);
|
|
2408
|
-
}
|
|
2409
|
-
await sleep(challenge.intervalMs + POLL_SAFETY_MARGIN_MS2);
|
|
2410
|
-
}
|
|
2411
|
-
throw new DeviceCodeError("timeout", "ChatGPT device authorization timed out");
|
|
2412
|
-
}
|
|
2413
|
-
async function exchangeCodexAuthorizationCode(code, codeVerifier, http) {
|
|
2414
|
-
const response = await http.fetch(`${OPENAI_CODEX_ISSUER}/oauth/token`, withSignal({
|
|
2415
|
-
method: "POST",
|
|
2416
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
|
|
2417
|
-
body: new URLSearchParams({
|
|
2418
|
-
grant_type: "authorization_code",
|
|
2419
|
-
code,
|
|
2420
|
-
redirect_uri: `${OPENAI_CODEX_ISSUER}/deviceauth/callback`,
|
|
2421
|
-
client_id: OPENAI_CODEX_CLIENT_ID,
|
|
2422
|
-
code_verifier: codeVerifier
|
|
2423
|
-
}).toString()
|
|
2424
|
-
}, http.signal));
|
|
2425
|
-
const json = await response.json().catch(() => ({}));
|
|
2426
|
-
if (!response.ok || typeof json !== "object" || json === null || typeof json.access_token !== "string") {
|
|
2427
|
-
throw new DeviceCodeError("failed", `ChatGPT token exchange failed (HTTP ${response.status})`);
|
|
2428
|
-
}
|
|
2429
|
-
const rec = json;
|
|
2430
|
-
return {
|
|
2431
|
-
accessToken: rec.access_token,
|
|
2432
|
-
...typeof rec.refresh_token === "string" ? { refreshToken: rec.refresh_token } : {},
|
|
2433
|
-
...typeof rec.expires_in === "number" ? { expiresInSeconds: rec.expires_in } : {}
|
|
2434
|
-
};
|
|
2435
|
-
}
|
|
2436
|
-
var OPENAI_CODEX_ISSUER = "https://auth.openai.com", OPENAI_CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann", POLL_SAFETY_MARGIN_MS2 = 3000;
|
|
2437
|
-
var init_openai_codex = __esm(() => {
|
|
2438
|
-
init_device_code();
|
|
2439
|
-
});
|
|
2440
|
-
|
|
2441
2706
|
// src/lib/oauth/login.ts
|
|
2442
2707
|
function httpOf(input) {
|
|
2443
2708
|
return {
|
|
@@ -2452,7 +2717,7 @@ function present(challenge, provider) {
|
|
|
2452
2717
|
userCode: challenge.userCode,
|
|
2453
2718
|
verificationUri: challenge.verificationUri,
|
|
2454
2719
|
...challenge.verificationUriComplete !== undefined ? { verificationUriComplete: challenge.verificationUriComplete } : {},
|
|
2455
|
-
instructions: `Open ${challenge.verificationUri} and enter code: ${challenge.userCode} (${deviceCodeMethodLabel(provider)})
|
|
2720
|
+
instructions: `Open ${challenge.verificationUri} and enter code: ${challenge.userCode} (${deviceCodeMethodLabel(provider)}).` + (provider === "openai-codex" ? " Enable device code login in ChatGPT Settings > Security if required. Cancel with Ctrl+C." : "")
|
|
2456
2721
|
};
|
|
2457
2722
|
}
|
|
2458
2723
|
async function loginRfc8628(input) {
|
|
@@ -2497,24 +2762,37 @@ async function loginDeviceCode(input) {
|
|
|
2497
2762
|
return { ok: false, error: `${input.provider} does not support device authorization` };
|
|
2498
2763
|
}
|
|
2499
2764
|
try {
|
|
2500
|
-
const tokens = input.provider === "openai" ? await loginCodex(input) : await loginRfc8628(input);
|
|
2765
|
+
const tokens = input.provider === "openai-codex" ? await loginCodex(input) : await loginRfc8628(input);
|
|
2501
2766
|
saveOAuthGrant(input.provider, grantFromTokens("device-code", tokens, input.now), input.dir);
|
|
2767
|
+
if (input.provider === "openai-codex" && loadOAuthGrant(input.provider, input.dir)?.access !== tokens.accessToken) {
|
|
2768
|
+
throw new DeviceCodeError("failed", "Could not save ChatGPT login securely. Check the Keryx config directory permissions.");
|
|
2769
|
+
}
|
|
2502
2770
|
return { ok: true, provider: input.provider };
|
|
2503
2771
|
} catch (err) {
|
|
2504
|
-
const message = err instanceof DeviceCodeError ? err.message : err instanceof Error ? err.message : "device authorization failed";
|
|
2772
|
+
const message = input.signal?.aborted === true ? "Device authorization was cancelled." : input.provider === "openai-codex" && !(err instanceof DeviceCodeError) ? "ChatGPT device authorization failed. Run `keryx auth login openai-codex` to retry." : err instanceof DeviceCodeError ? err.message : err instanceof Error ? err.message : "device authorization failed";
|
|
2505
2773
|
return { ok: false, error: message };
|
|
2506
2774
|
}
|
|
2507
2775
|
}
|
|
2508
2776
|
async function loginCodex(input) {
|
|
2509
|
-
const
|
|
2777
|
+
const timeout = AbortSignal.timeout(15 * 60 * 1000);
|
|
2778
|
+
const http = { ...httpOf(input), signal: input.signal === undefined ? timeout : AbortSignal.any([input.signal, timeout]) };
|
|
2510
2779
|
const challenge = await requestCodexDeviceCode(http);
|
|
2511
2780
|
input.onChallenge(present({
|
|
2512
2781
|
userCode: challenge.userCode,
|
|
2513
2782
|
verificationUri: challenge.verificationUri
|
|
2514
|
-
}, "openai"));
|
|
2515
|
-
|
|
2783
|
+
}, "openai-codex"));
|
|
2784
|
+
const tokens = await pollCodexDeviceToken(challenge, http);
|
|
2785
|
+
if (tokens.accountId === undefined) {
|
|
2786
|
+
throw new DeviceCodeError("failed", "ChatGPT login returned no account identity. Run `keryx auth login openai-codex` to retry.");
|
|
2787
|
+
}
|
|
2788
|
+
return tokens;
|
|
2516
2789
|
}
|
|
2517
2790
|
async function refreshProviderGrant(provider, http, dir) {
|
|
2791
|
+
if (provider === "openai-codex" || provider === "openai") {
|
|
2792
|
+
if (loadOAuthGrant("openai-codex", dir) === undefined)
|
|
2793
|
+
return;
|
|
2794
|
+
return refreshOpenAiCodexGrant({ ...http, ...dir !== undefined ? { configDir: dir } : {} });
|
|
2795
|
+
}
|
|
2518
2796
|
const grant = loadOAuthGrant(provider, dir);
|
|
2519
2797
|
if (grant === undefined || !grantNeedsRefresh(grant, http.now)) {
|
|
2520
2798
|
return grant;
|
|
@@ -2537,9 +2815,6 @@ async function refreshProviderGrant(provider, http, dir) {
|
|
|
2537
2815
|
saveOAuthGrant(provider, next, dir);
|
|
2538
2816
|
return next;
|
|
2539
2817
|
}
|
|
2540
|
-
if (provider === "openai") {
|
|
2541
|
-
return grant;
|
|
2542
|
-
}
|
|
2543
2818
|
const tokens = await refreshAccessToken({
|
|
2544
2819
|
tokenEndpoint: endpoints.tokenEndpoint,
|
|
2545
2820
|
clientId: endpoints.clientId,
|
|
@@ -2566,7 +2841,7 @@ async function refreshSavedGrants(http, dir, providers = SESSION_REFRESHED_GRANT
|
|
|
2566
2841
|
}
|
|
2567
2842
|
continue;
|
|
2568
2843
|
}
|
|
2569
|
-
if (!grantNeedsRefresh(grant, http.now))
|
|
2844
|
+
if (!grantNeedsRefresh(grant, http.now) && !(provider === "openai-codex" && grant.expires === undefined))
|
|
2570
2845
|
continue;
|
|
2571
2846
|
try {
|
|
2572
2847
|
await refreshProviderGrant(provider, http, dir);
|
|
@@ -2578,7 +2853,7 @@ async function refreshSavedGrants(http, dir, providers = SESSION_REFRESHED_GRANT
|
|
|
2578
2853
|
return warnings;
|
|
2579
2854
|
}
|
|
2580
2855
|
function logoutProvider(provider, dir) {
|
|
2581
|
-
deleteOAuthGrant(provider, dir);
|
|
2856
|
+
deleteOAuthGrant(provider === "openai" ? "openai-codex" : provider, dir);
|
|
2582
2857
|
}
|
|
2583
2858
|
var SESSION_REFRESHED_GRANTS;
|
|
2584
2859
|
var init_login = __esm(() => {
|
|
@@ -2588,7 +2863,8 @@ var init_login = __esm(() => {
|
|
|
2588
2863
|
init_device_code();
|
|
2589
2864
|
init_grants();
|
|
2590
2865
|
init_openai_codex();
|
|
2591
|
-
|
|
2866
|
+
init_openai_subscription();
|
|
2867
|
+
SESSION_REFRESHED_GRANTS = ["grok", "github-copilot", "openai-codex"];
|
|
2592
2868
|
});
|
|
2593
2869
|
|
|
2594
2870
|
// src/lib/caller-session.ts
|
|
@@ -3635,6 +3911,40 @@ var init_provider_catalog_cache = __esm(() => {
|
|
|
3635
3911
|
CATALOG_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
3636
3912
|
});
|
|
3637
3913
|
|
|
3914
|
+
// src/lib/oauth/open-url.ts
|
|
3915
|
+
import { spawn } from "child_process";
|
|
3916
|
+
function browserOpenPlan(url, platform = process.platform, env = process.env) {
|
|
3917
|
+
if (url.length === 0) {
|
|
3918
|
+
return;
|
|
3919
|
+
}
|
|
3920
|
+
if (platform === "darwin") {
|
|
3921
|
+
return { cmd: "open", args: [url] };
|
|
3922
|
+
}
|
|
3923
|
+
if (platform === "win32") {
|
|
3924
|
+
return { cmd: "cmd", args: ["/c", "start", "", url] };
|
|
3925
|
+
}
|
|
3926
|
+
if (typeof env.DISPLAY === "string" && env.DISPLAY.length > 0) {
|
|
3927
|
+
return { cmd: "xdg-open", args: [url] };
|
|
3928
|
+
}
|
|
3929
|
+
if (typeof env.WAYLAND_DISPLAY === "string" && env.WAYLAND_DISPLAY.length > 0) {
|
|
3930
|
+
return { cmd: "xdg-open", args: [url] };
|
|
3931
|
+
}
|
|
3932
|
+
return;
|
|
3933
|
+
}
|
|
3934
|
+
function openVerificationUrl(url, deps = {}) {
|
|
3935
|
+
const plan = browserOpenPlan(url, deps.platform ?? process.platform, deps.env ?? process.env);
|
|
3936
|
+
if (plan === undefined) {
|
|
3937
|
+
return;
|
|
3938
|
+
}
|
|
3939
|
+
const spawnFn = deps.spawn ?? spawn;
|
|
3940
|
+
try {
|
|
3941
|
+
const child = spawnFn(plan.cmd, plan.args, { stdio: "ignore", detached: true });
|
|
3942
|
+
child.on("error", () => {});
|
|
3943
|
+
child.unref();
|
|
3944
|
+
} catch {}
|
|
3945
|
+
}
|
|
3946
|
+
var init_open_url = () => {};
|
|
3947
|
+
|
|
3638
3948
|
// src/harness/policy/engine.ts
|
|
3639
3949
|
import path8 from "path";
|
|
3640
3950
|
function baseOutcomeFor(risk, defaults) {
|
|
@@ -4151,15 +4461,12 @@ async function detectProviders(deps) {
|
|
|
4151
4461
|
if (typeof anthropicKey === "string" && anthropicKey.length > 0) {
|
|
4152
4462
|
detected.push({ name: "anthropic", models: [...ANTHROPIC_MODELS] });
|
|
4153
4463
|
}
|
|
4154
|
-
|
|
4155
|
-
if (typeof openaiKey === "string" && openaiKey.length > 0) {
|
|
4156
|
-
detected.push({ name: "openai", models: [...OPENAI_MODELS] });
|
|
4157
|
-
}
|
|
4464
|
+
detected.push({ name: OPENAI_CODEX_PICKER.name, label: OPENAI_CODEX_PICKER.label, models: [...OPENAI_CODEX_PICKER.models], note: "ChatGPT subscription \xB7 browser device login" });
|
|
4158
4465
|
const geminiKey = deps.env.GEMINI_API_KEY ?? deps.env.GOOGLE_API_KEY;
|
|
4159
4466
|
if (typeof geminiKey === "string" && geminiKey.length > 0) {
|
|
4160
4467
|
detected.push({ name: "gemini", models: [...GEMINI_MODELS] });
|
|
4161
4468
|
}
|
|
4162
|
-
for (const p of allOpenAiCompatProviders()) {
|
|
4469
|
+
for (const p of allOpenAiCompatProviders(deps.configDir)) {
|
|
4163
4470
|
if (!isProviderPlatformSupported(p, platform)) {
|
|
4164
4471
|
continue;
|
|
4165
4472
|
}
|
|
@@ -4219,6 +4526,31 @@ function parseChoice(line, count) {
|
|
|
4219
4526
|
function toSelection(provider, model) {
|
|
4220
4527
|
return provider.baseUrl === undefined ? { provider: provider.name, model } : { provider: provider.name, model, baseUrl: provider.baseUrl };
|
|
4221
4528
|
}
|
|
4529
|
+
async function loginSubscriptionInReadline(io, deps, fetchFn) {
|
|
4530
|
+
const controller = new AbortController;
|
|
4531
|
+
const cancel = () => controller.abort();
|
|
4532
|
+
const signal = deps.signal === undefined ? controller.signal : AbortSignal.any([controller.signal, deps.signal]);
|
|
4533
|
+
process.on("SIGINT", cancel);
|
|
4534
|
+
try {
|
|
4535
|
+
io.write(`Connect your ChatGPT subscription (Ctrl+C to cancel).
|
|
4536
|
+
`);
|
|
4537
|
+
const result = await loginDeviceCode({
|
|
4538
|
+
provider: "openai-codex",
|
|
4539
|
+
fetch: (url, init) => fetchFn(url, init),
|
|
4540
|
+
signal,
|
|
4541
|
+
...deps.configDir === undefined ? {} : { dir: deps.configDir },
|
|
4542
|
+
onChallenge: (challenge) => {
|
|
4543
|
+
io.write(`${challenge.instructions}
|
|
4544
|
+
`);
|
|
4545
|
+
(deps.openVerificationUrl ?? openVerificationUrl)(challenge.verificationUriComplete ?? challenge.verificationUri);
|
|
4546
|
+
}
|
|
4547
|
+
});
|
|
4548
|
+
if (!result.ok)
|
|
4549
|
+
throw new SubscriptionSelectionError(signal.aborted ? "ChatGPT subscription login cancelled" : result.error);
|
|
4550
|
+
} finally {
|
|
4551
|
+
process.removeListener("SIGINT", cancel);
|
|
4552
|
+
}
|
|
4553
|
+
}
|
|
4222
4554
|
async function pickProviderModel(io, detected, deps = {}) {
|
|
4223
4555
|
const fetchFn = deps.fetch ?? globalThis.fetch;
|
|
4224
4556
|
const env = deps.env ?? process.env;
|
|
@@ -4266,9 +4598,20 @@ async function pickProviderModel(io, detected, deps = {}) {
|
|
|
4266
4598
|
chosenProvider = provider;
|
|
4267
4599
|
break;
|
|
4268
4600
|
}
|
|
4601
|
+
if (chosenProvider.name === "openai-codex" && loadOAuthGrant("openai-codex", deps.configDir) === undefined) {
|
|
4602
|
+
await loginSubscriptionInReadline(io, deps, fetchFn);
|
|
4603
|
+
}
|
|
4269
4604
|
io.write(`Fetching models for ${chosenProvider.name}\u2026
|
|
4270
4605
|
`);
|
|
4271
|
-
const
|
|
4606
|
+
const modelOptions = deps.configDir === undefined ? {} : { configDir: deps.configDir };
|
|
4607
|
+
let resolved = await resolveModelsForPicker(fetchFn, chosenProvider, env, modelOptions);
|
|
4608
|
+
if (chosenProvider.name === "openai-codex" && resolved.failure?.kind === "rejected") {
|
|
4609
|
+
await loginSubscriptionInReadline(io, deps, fetchFn);
|
|
4610
|
+
resolved = await resolveModelsForPicker(fetchFn, chosenProvider, env, modelOptions);
|
|
4611
|
+
}
|
|
4612
|
+
if (chosenProvider.name === "openai-codex" && (resolved.source !== "live" || resolved.models.length === 0)) {
|
|
4613
|
+
throw new SubscriptionSelectionError("ChatGPT subscription models are unavailable. Try again or run `keryx auth login openai-codex`.");
|
|
4614
|
+
}
|
|
4272
4615
|
const models = resolved.models.length > 0 ? resolved.models : chosenProvider.models;
|
|
4273
4616
|
if (resolved.source === "live") {
|
|
4274
4617
|
io.write(` (${models.length} model(s) from API)
|
|
@@ -4303,12 +4646,18 @@ async function pickProviderModel(io, detected, deps = {}) {
|
|
|
4303
4646
|
return toSelection(chosenProvider, model);
|
|
4304
4647
|
}
|
|
4305
4648
|
}
|
|
4306
|
-
var DEFAULT_OLLAMA_BASE_URL = "http://localhost:11434", FAKE_MODELS;
|
|
4649
|
+
var DEFAULT_OLLAMA_BASE_URL = "http://localhost:11434", FAKE_MODELS, SubscriptionSelectionError;
|
|
4307
4650
|
var init_select = __esm(() => {
|
|
4651
|
+
init_subscription_models();
|
|
4652
|
+
init_grants();
|
|
4653
|
+
init_login();
|
|
4654
|
+
init_open_url();
|
|
4308
4655
|
init_guard();
|
|
4309
4656
|
init_providers();
|
|
4310
4657
|
init_curated_model_lists();
|
|
4311
4658
|
FAKE_MODELS = ["fake-echo"];
|
|
4659
|
+
SubscriptionSelectionError = class SubscriptionSelectionError extends Error {
|
|
4660
|
+
};
|
|
4312
4661
|
});
|
|
4313
4662
|
|
|
4314
4663
|
// src/harness/provider-catalog.ts
|
|
@@ -4326,6 +4675,12 @@ async function buildCatalogEntry(detected, deps, fetchedAt) {
|
|
|
4326
4675
|
...detected.label !== undefined ? { label: detected.label } : {}
|
|
4327
4676
|
};
|
|
4328
4677
|
}
|
|
4678
|
+
if (detected.name === "openai-codex") {
|
|
4679
|
+
if (loadOAuthGrant("openai-codex", deps.dir) === undefined)
|
|
4680
|
+
return;
|
|
4681
|
+
const result = await fetchOpenAiCodexModels(deps.fetch, { ...deps.dir !== undefined ? { configDir: deps.dir } : {}, timeoutMs: deps.timeoutMs ?? CATALOG_FETCH_TIMEOUT_MS });
|
|
4682
|
+
return { name: detected.name, status: classifyModelsStatus(result), models: result.models, fallbackModels: [...detected.models], fetchedAt, label: detected.label ?? "ChatGPT / Codex" };
|
|
4683
|
+
}
|
|
4329
4684
|
const registry = providerByName(detected.name, deps.dir);
|
|
4330
4685
|
if (registry === undefined) {
|
|
4331
4686
|
return {
|
|
@@ -4338,7 +4693,7 @@ async function buildCatalogEntry(detected, deps, fetchedAt) {
|
|
|
4338
4693
|
};
|
|
4339
4694
|
}
|
|
4340
4695
|
const requiresApiKey = registry.requiresApiKey ?? true;
|
|
4341
|
-
const apiKey = providerApiKey(registry, deps.env) ?? registry.apiKey;
|
|
4696
|
+
const apiKey = providerApiKey(registry, envWithSavedApiKeys(deps.env, deps.dir)) ?? registry.apiKey;
|
|
4342
4697
|
if (requiresApiKey && (apiKey === undefined || apiKey.length === 0)) {
|
|
4343
4698
|
return;
|
|
4344
4699
|
}
|
|
@@ -4375,6 +4730,7 @@ async function refreshProviderCatalog(deps) {
|
|
|
4375
4730
|
const detectDeps = {
|
|
4376
4731
|
fetch: deps.fetch,
|
|
4377
4732
|
env: deps.env,
|
|
4733
|
+
...deps.dir !== undefined ? { configDir: deps.dir } : {},
|
|
4378
4734
|
...deps.baseUrl !== undefined ? { baseUrl: deps.baseUrl } : {},
|
|
4379
4735
|
...deps.platform !== undefined ? { platform: deps.platform } : {}
|
|
4380
4736
|
};
|
|
@@ -4446,6 +4802,9 @@ function describeCatalogStatus(status) {
|
|
|
4446
4802
|
}
|
|
4447
4803
|
var CATALOG_FETCH_TIMEOUT_MS = 8000;
|
|
4448
4804
|
var init_provider_catalog = __esm(() => {
|
|
4805
|
+
init_grants();
|
|
4806
|
+
init_shell_config();
|
|
4807
|
+
init_subscription_models();
|
|
4449
4808
|
init_select();
|
|
4450
4809
|
init_providers();
|
|
4451
4810
|
init_provider_catalog_cache();
|
|
@@ -4511,8 +4870,11 @@ function resolveProviderModelParamsByName(name, shellConfig = loadShellConfig(),
|
|
|
4511
4870
|
function providerByName(name, dir) {
|
|
4512
4871
|
return allOpenAiCompatProviders(dir).find((p) => p.name === name);
|
|
4513
4872
|
}
|
|
4873
|
+
function connectionProviderByName(name, dir) {
|
|
4874
|
+
return name === "openai-codex" ? OPENAI_CODEX_PICKER : providerByName(name, dir);
|
|
4875
|
+
}
|
|
4514
4876
|
function customCompatProviders(dir) {
|
|
4515
|
-
const builtinNames = new Set(OPENAI_COMPAT_PROVIDERS.map((p) => p.name));
|
|
4877
|
+
const builtinNames = new Set([...OPENAI_COMPAT_PROVIDERS.map((p) => p.name), "openai-codex"]);
|
|
4516
4878
|
return loadCustomCompatProviders(dir).filter((p) => !builtinNames.has(p.name)).map((p) => ({
|
|
4517
4879
|
name: p.name,
|
|
4518
4880
|
label: p.label ?? p.name,
|
|
@@ -4690,12 +5052,14 @@ async function fetchOpenAiCompatModelsDetailed(fetchFn, provider, apiKey, opts)
|
|
|
4690
5052
|
}
|
|
4691
5053
|
}
|
|
4692
5054
|
async function resolveModelsForPicker(fetchFn, provider, env = process.env, opts) {
|
|
4693
|
-
|
|
5055
|
+
if (provider.name === "openai-codex")
|
|
5056
|
+
return fetchOpenAiCodexModels(fetchFn, opts);
|
|
5057
|
+
const compat = providerByName(provider.name, opts?.configDir);
|
|
4694
5058
|
if (compat === undefined) {
|
|
4695
5059
|
return { models: [...provider.models], source: "fallback" };
|
|
4696
5060
|
}
|
|
4697
5061
|
const envKey = provider.envKey ?? compat.envKey;
|
|
4698
|
-
const raw = envKey === undefined ? undefined : env[envKey];
|
|
5062
|
+
const raw = envKey === undefined ? undefined : envWithSavedApiKeys(env, opts?.configDir)[envKey];
|
|
4699
5063
|
const apiKey = typeof raw === "string" && raw.length > 0 ? raw : compat.apiKey;
|
|
4700
5064
|
return fetchOpenAiCompatModelsDetailed(fetchFn, { ...compat, ...provider.baseUrl !== undefined ? { baseUrl: provider.baseUrl } : {} }, apiKey, opts);
|
|
4701
5065
|
}
|
|
@@ -4819,7 +5183,9 @@ function providerApiKey(provider, env = process.env) {
|
|
|
4819
5183
|
return typeof raw === "string" && raw.length > 0 ? raw : undefined;
|
|
4820
5184
|
}
|
|
4821
5185
|
async function testProviderConnection(provider, fetchFn = globalThis.fetch, env = process.env, profiles) {
|
|
4822
|
-
|
|
5186
|
+
if (provider.name === "openai-codex")
|
|
5187
|
+
return fetchOpenAiCodexModels(fetchFn, { ...profiles?.dir !== undefined ? { configDir: profiles.dir } : {} });
|
|
5188
|
+
const apiKey = providerApiKey(provider, envWithSavedApiKeys(env, profiles?.dir)) ?? provider.apiKey;
|
|
4823
5189
|
return fetchOpenAiCompatModelsDetailed(fetchFn, provider, apiKey, {
|
|
4824
5190
|
timeoutMs: MODELS_FETCH_TIMEOUT_MS,
|
|
4825
5191
|
...profiles !== undefined ? { refreshProfiles: profiles } : {}
|
|
@@ -4837,7 +5203,7 @@ function classifyProviderConnection(name, env = process.env, dir) {
|
|
|
4837
5203
|
if (customCompatProviders(dir).some((p) => p.name === name)) {
|
|
4838
5204
|
return { kind: "custom", sharedWith: [] };
|
|
4839
5205
|
}
|
|
4840
|
-
if (
|
|
5206
|
+
if (name !== "openai" && loadOAuthGrant(name, dir) !== undefined) {
|
|
4841
5207
|
const envKey = oauthEnvKeyFor(name);
|
|
4842
5208
|
const sharedWith = envKey === undefined ? [] : providersSharingEnvKey(envKey, name, allOpenAiCompatProviders(dir));
|
|
4843
5209
|
return { kind: "oauth-grant", ...envKey !== undefined ? { envKey } : {}, sharedWith };
|
|
@@ -4905,7 +5271,7 @@ function configuredProviders(env = process.env, dir) {
|
|
|
4905
5271
|
if (provider.requiresApiKey === false)
|
|
4906
5272
|
return true;
|
|
4907
5273
|
return providerApiKey(provider, env) !== undefined || provider.apiKey !== undefined;
|
|
4908
|
-
}).filter((provider) => isProviderPlatformSupported(provider)).map((provider) => ({ name: provider.name, models: provider.models }));
|
|
5274
|
+
}).filter((provider) => isProviderPlatformSupported(provider)).map((provider) => ({ name: provider.name, models: provider.models })).concat(loadOAuthGrant("openai-codex", dir) === undefined ? [] : [{ name: "openai-codex", models: [...OPENAI_CODEX_PICKER.models] }]);
|
|
4909
5275
|
}
|
|
4910
5276
|
async function providersCommand(args, deps = {}) {
|
|
4911
5277
|
const command = args[0];
|
|
@@ -4945,7 +5311,7 @@ async function runProvidersTest(args, deps) {
|
|
|
4945
5311
|
return;
|
|
4946
5312
|
}
|
|
4947
5313
|
const dir = deps.dir;
|
|
4948
|
-
const provider =
|
|
5314
|
+
const provider = connectionProviderByName(name, dir);
|
|
4949
5315
|
if (provider === undefined) {
|
|
4950
5316
|
console.error(`Unknown provider: ${name}`);
|
|
4951
5317
|
process.exitCode = 1;
|
|
@@ -5017,7 +5383,7 @@ async function runProvidersRemove(args, deps) {
|
|
|
5017
5383
|
const dir = deps.dir;
|
|
5018
5384
|
const env = deps.env ?? process.env;
|
|
5019
5385
|
const json = args.includes("--json");
|
|
5020
|
-
if (
|
|
5386
|
+
if (connectionProviderByName(name, dir) === undefined) {
|
|
5021
5387
|
if (json) {
|
|
5022
5388
|
console.log(JSON.stringify({ provider: name, ok: false, reason: "unknown provider" }, null, 2));
|
|
5023
5389
|
} else {
|
|
@@ -5218,6 +5584,8 @@ var init_providers = __esm(() => {
|
|
|
5218
5584
|
init_provider_config();
|
|
5219
5585
|
init_catalog();
|
|
5220
5586
|
init_model_profile();
|
|
5587
|
+
init_subscription_models();
|
|
5588
|
+
init_curated_model_lists();
|
|
5221
5589
|
init_grants();
|
|
5222
5590
|
init_login();
|
|
5223
5591
|
init_caller_session();
|
|
@@ -5228,6 +5596,7 @@ var init_providers = __esm(() => {
|
|
|
5228
5596
|
MINIMAX_REASONING_PRESET_HOSTS = new Set(["api.minimax.io", "api.minimaxi.com"]);
|
|
5229
5597
|
DEEPSEEK_REASONING_PRESET_HOSTS = new Set(["api.deepseek.com"]);
|
|
5230
5598
|
OPENAI_COMPAT_PROVIDERS = [
|
|
5599
|
+
{ name: "openai", label: "OpenAI API", baseUrl: "https://api.openai.com", envKey: "OPENAI_API_KEY", models: [...OPENAI_MODELS], note: "Platform API key \xB7 separate API billing" },
|
|
5231
5600
|
{
|
|
5232
5601
|
name: "openrouter",
|
|
5233
5602
|
label: "OpenRouter",
|
|
@@ -8796,7 +9165,7 @@ var init_redact = __esm(() => {
|
|
|
8796
9165
|
});
|
|
8797
9166
|
|
|
8798
9167
|
// src/security/output-validation.ts
|
|
8799
|
-
function
|
|
9168
|
+
function failure2(reason) {
|
|
8800
9169
|
return {
|
|
8801
9170
|
ok: false,
|
|
8802
9171
|
text: FORMAT_UNSAFE_OUTPUT_TEXT,
|
|
@@ -9057,12 +9426,12 @@ function validateOutputForTransport(input) {
|
|
|
9057
9426
|
try {
|
|
9058
9427
|
if (input.format === "text") {
|
|
9059
9428
|
if (typeof input.value !== "string") {
|
|
9060
|
-
return
|
|
9429
|
+
return failure2("non-text-value");
|
|
9061
9430
|
}
|
|
9062
9431
|
const safe = redactString(input.value, undefined, input.exemptExfil);
|
|
9063
9432
|
const schemaReason = schemaFailure(safe.value, input.schema);
|
|
9064
9433
|
if (schemaReason !== undefined)
|
|
9065
|
-
return
|
|
9434
|
+
return failure2(schemaReason);
|
|
9066
9435
|
const reasons = uniqueSorted(safe.reasons);
|
|
9067
9436
|
return {
|
|
9068
9437
|
ok: true,
|
|
@@ -9076,10 +9445,10 @@ function validateOutputForTransport(input) {
|
|
|
9076
9445
|
}
|
|
9077
9446
|
const safe = sanitizeJsonValue(input.value, undefined, new WeakSet, input.exemptExfil);
|
|
9078
9447
|
if (!safe.ok)
|
|
9079
|
-
return
|
|
9448
|
+
return failure2(safe.reason);
|
|
9080
9449
|
const schemaReason = schemaFailure(safe.value, input.schema);
|
|
9081
9450
|
if (schemaReason !== undefined)
|
|
9082
|
-
return
|
|
9451
|
+
return failure2(schemaReason);
|
|
9083
9452
|
const reasons = uniqueSorted(safe.reasons);
|
|
9084
9453
|
return {
|
|
9085
9454
|
ok: true,
|
|
@@ -9091,7 +9460,7 @@ function validateOutputForTransport(input) {
|
|
|
9091
9460
|
}
|
|
9092
9461
|
};
|
|
9093
9462
|
} catch {
|
|
9094
|
-
return
|
|
9463
|
+
return failure2("validation.error");
|
|
9095
9464
|
}
|
|
9096
9465
|
}
|
|
9097
9466
|
function skipWhitespace(scanner) {
|
|
@@ -17259,7 +17628,7 @@ var package_default;
|
|
|
17259
17628
|
var init_package = __esm(() => {
|
|
17260
17629
|
package_default = {
|
|
17261
17630
|
name: "@mrciphersmith/keryx",
|
|
17262
|
-
version: "0.3.
|
|
17631
|
+
version: "0.3.6",
|
|
17263
17632
|
description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
17264
17633
|
private: false,
|
|
17265
17634
|
publishConfig: {
|
|
@@ -21823,14 +22192,14 @@ var init_config2 = __esm(() => {
|
|
|
21823
22192
|
// src/sync/provenance.ts
|
|
21824
22193
|
import { mkdir as mkdir9, readFile as readFile20, writeFile as writeFile7 } from "fs/promises";
|
|
21825
22194
|
import path52 from "path";
|
|
21826
|
-
import { spawn } from "child_process";
|
|
22195
|
+
import { spawn as spawn2 } from "child_process";
|
|
21827
22196
|
function provenancePath(cwd, module) {
|
|
21828
22197
|
return path52.join(cwd, ".metaproject", "data", module, ".provenance.json");
|
|
21829
22198
|
}
|
|
21830
22199
|
function gitCmdResult(cwd, args) {
|
|
21831
22200
|
return new Promise((resolve) => {
|
|
21832
22201
|
try {
|
|
21833
|
-
const child =
|
|
22202
|
+
const child = spawn2("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
21834
22203
|
let out = "";
|
|
21835
22204
|
let err = "";
|
|
21836
22205
|
child.stdout?.on("data", (chunk) => {
|
|
@@ -21940,13 +22309,13 @@ var init_provenance = __esm(() => {
|
|
|
21940
22309
|
});
|
|
21941
22310
|
|
|
21942
22311
|
// src/gdgraph/staleness.ts
|
|
21943
|
-
import { spawn as
|
|
22312
|
+
import { spawn as spawn3 } from "child_process";
|
|
21944
22313
|
import { stat as stat8 } from "fs/promises";
|
|
21945
22314
|
import path53 from "path";
|
|
21946
22315
|
function gitStatusPorcelain(cwd) {
|
|
21947
22316
|
return new Promise((resolve) => {
|
|
21948
22317
|
try {
|
|
21949
|
-
const child =
|
|
22318
|
+
const child = spawn3("git", ["status", "--porcelain=v1", "-z"], { cwd, stdio: ["ignore", "pipe", "ignore"] });
|
|
21950
22319
|
let out = "";
|
|
21951
22320
|
child.stdout?.on("data", (chunk) => {
|
|
21952
22321
|
out += String(chunk);
|
|
@@ -30016,14 +30385,14 @@ function asNumber5(value) {
|
|
|
30016
30385
|
function clampOpenAiReasoningEffort(effort) {
|
|
30017
30386
|
return effort === "xhigh" || effort === "max" ? "high" : effort;
|
|
30018
30387
|
}
|
|
30019
|
-
function ownedReasoningItems(reasoning) {
|
|
30388
|
+
function ownedReasoningItems(reasoning, providerId) {
|
|
30020
30389
|
const replay = reasoning?.replay;
|
|
30021
30390
|
if (replay === undefined) {
|
|
30022
30391
|
return [];
|
|
30023
30392
|
}
|
|
30024
30393
|
const items = [];
|
|
30025
30394
|
for (const item of replay) {
|
|
30026
|
-
if (item.providerId !==
|
|
30395
|
+
if (item.providerId !== providerId || item.kind !== "reasoning_item") {
|
|
30027
30396
|
continue;
|
|
30028
30397
|
}
|
|
30029
30398
|
if (isPlainObject7(item.data)) {
|
|
@@ -30032,7 +30401,7 @@ function ownedReasoningItems(reasoning) {
|
|
|
30032
30401
|
}
|
|
30033
30402
|
return items;
|
|
30034
30403
|
}
|
|
30035
|
-
function toResponsesInput(messages) {
|
|
30404
|
+
function toResponsesInput(messages, providerId) {
|
|
30036
30405
|
const out = [];
|
|
30037
30406
|
for (const linked of linkToolCalls(messages)) {
|
|
30038
30407
|
const message = linked.message;
|
|
@@ -30048,7 +30417,7 @@ function toResponsesInput(messages) {
|
|
|
30048
30417
|
continue;
|
|
30049
30418
|
}
|
|
30050
30419
|
if (message.role === "assistant") {
|
|
30051
|
-
for (const item of ownedReasoningItems(message.reasoning)) {
|
|
30420
|
+
for (const item of ownedReasoningItems(message.reasoning, providerId)) {
|
|
30052
30421
|
out.push(item);
|
|
30053
30422
|
}
|
|
30054
30423
|
}
|
|
@@ -30136,14 +30505,23 @@ function classifyHttpError3(status, headers, code) {
|
|
|
30136
30505
|
}
|
|
30137
30506
|
return { kind: "unknown", retryable: retryableFor4("unknown", false), message: "" };
|
|
30138
30507
|
}
|
|
30139
|
-
async function raceReadAgainstDeadline4(reader, ms) {
|
|
30508
|
+
async function raceReadAgainstDeadline4(reader, ms, signal) {
|
|
30509
|
+
if (signal?.aborted === true)
|
|
30510
|
+
return READ_CANCELLED;
|
|
30140
30511
|
let timer;
|
|
30141
30512
|
const deadline = new Promise((resolve) => {
|
|
30142
30513
|
timer = setTimeout(() => resolve(READ_TIMED_OUT4), ms);
|
|
30143
30514
|
});
|
|
30515
|
+
let onAbort;
|
|
30516
|
+
const aborted = new Promise((resolve) => {
|
|
30517
|
+
onAbort = () => resolve(READ_CANCELLED);
|
|
30518
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
30519
|
+
});
|
|
30144
30520
|
try {
|
|
30145
|
-
return await Promise.race([reader.read(), deadline]);
|
|
30521
|
+
return await Promise.race([reader.read(), deadline, aborted]);
|
|
30146
30522
|
} finally {
|
|
30523
|
+
if (onAbort !== undefined)
|
|
30524
|
+
signal?.removeEventListener("abort", onAbort);
|
|
30147
30525
|
if (timer !== undefined) {
|
|
30148
30526
|
clearTimeout(timer);
|
|
30149
30527
|
}
|
|
@@ -30165,6 +30543,9 @@ async function* drainAndCheckAbort4(queue, signal, stamp) {
|
|
|
30165
30543
|
|
|
30166
30544
|
class OpenAiProvider {
|
|
30167
30545
|
deps;
|
|
30546
|
+
get providerId() {
|
|
30547
|
+
return this.deps.codex === undefined ? PROVIDER_ID2 : "openai-codex";
|
|
30548
|
+
}
|
|
30168
30549
|
constructor(deps) {
|
|
30169
30550
|
this.deps = deps;
|
|
30170
30551
|
}
|
|
@@ -30182,15 +30563,15 @@ class OpenAiProvider {
|
|
|
30182
30563
|
};
|
|
30183
30564
|
return {
|
|
30184
30565
|
capabilities,
|
|
30185
|
-
descriptor: { providerId:
|
|
30566
|
+
descriptor: { providerId: this.providerId, providerRevision: PROVIDER_REVISION4 }
|
|
30186
30567
|
};
|
|
30187
30568
|
}
|
|
30188
30569
|
descriptorDocument() {
|
|
30189
30570
|
return {
|
|
30190
30571
|
schemaVersion: 1,
|
|
30191
|
-
providerId:
|
|
30572
|
+
providerId: this.providerId,
|
|
30192
30573
|
providerRevision: PROVIDER_REVISION4,
|
|
30193
|
-
models: [{ modelId: DEFAULT_MODEL4.modelId, revision: DEFAULT_MODEL4.revision }],
|
|
30574
|
+
models: [{ modelId: this.deps.codex === undefined ? DEFAULT_MODEL4.modelId : "gpt-5.4", revision: DEFAULT_MODEL4.revision }],
|
|
30194
30575
|
capabilities: {
|
|
30195
30576
|
streaming: true,
|
|
30196
30577
|
tools: true,
|
|
@@ -30215,7 +30596,8 @@ class OpenAiProvider {
|
|
|
30215
30596
|
});
|
|
30216
30597
|
return;
|
|
30217
30598
|
}
|
|
30218
|
-
const
|
|
30599
|
+
const codex = this.deps.codex;
|
|
30600
|
+
const baseUrl = codex === undefined ? grant.baseUrl ?? DEFAULT_BASE_URL4 : "https://chatgpt.com/backend-api/codex";
|
|
30219
30601
|
let host;
|
|
30220
30602
|
try {
|
|
30221
30603
|
host = new URL(baseUrl).hostname;
|
|
@@ -30230,10 +30612,16 @@ class OpenAiProvider {
|
|
|
30230
30612
|
});
|
|
30231
30613
|
return;
|
|
30232
30614
|
}
|
|
30233
|
-
const url = `${baseUrl.replace(/\/+$/, "")}/v1/responses`;
|
|
30615
|
+
const url = `${baseUrl.replace(/\/+$/, "")}${codex === undefined ? "/v1/responses" : "/responses"}`;
|
|
30234
30616
|
const headers = {
|
|
30235
30617
|
authorization: `Bearer ${grant.apiKey}`,
|
|
30236
|
-
"content-type": "application/json"
|
|
30618
|
+
"content-type": "application/json",
|
|
30619
|
+
...codex === undefined ? {} : {
|
|
30620
|
+
"ChatGPT-Account-ID": codex.accountId,
|
|
30621
|
+
originator: "keryx",
|
|
30622
|
+
"User-Agent": "keryx",
|
|
30623
|
+
accept: "text/event-stream"
|
|
30624
|
+
}
|
|
30237
30625
|
};
|
|
30238
30626
|
const requestedReasoningEffort = request.options?.reasoning;
|
|
30239
30627
|
const reasoningRequested = typeof requestedReasoningEffort === "string" && requestedReasoningEffort.length > 0 && requestedReasoningEffort !== "off";
|
|
@@ -30241,10 +30629,10 @@ class OpenAiProvider {
|
|
|
30241
30629
|
const payload = {
|
|
30242
30630
|
model: request.modelId,
|
|
30243
30631
|
instructions: request.systemInstruction,
|
|
30244
|
-
input: toResponsesInput(request.messages),
|
|
30632
|
+
input: toResponsesInput(request.messages, this.providerId),
|
|
30245
30633
|
stream: true,
|
|
30246
|
-
max_output_tokens: request.budget.maxOutputTokens,
|
|
30247
|
-
...request.options?.temperature !== undefined && !reasoningRequested ? { temperature: request.options.temperature } : {},
|
|
30634
|
+
...codex === undefined ? { max_output_tokens: request.budget.maxOutputTokens } : { store: false, tool_choice: "auto", parallel_tool_calls: true, tools: [] },
|
|
30635
|
+
...codex === undefined && request.options?.temperature !== undefined && !reasoningRequested ? { temperature: request.options.temperature } : {},
|
|
30248
30636
|
...reasoningRequested ? {
|
|
30249
30637
|
reasoning: { effort: reasoningEffort, summary: "auto" },
|
|
30250
30638
|
include: ["reasoning.encrypted_content"],
|
|
@@ -30328,7 +30716,7 @@ class OpenAiProvider {
|
|
|
30328
30716
|
const timeoutMs = receivedAnyChunk ? idleTimeoutMs : firstByteTimeoutMs;
|
|
30329
30717
|
let readResult;
|
|
30330
30718
|
try {
|
|
30331
|
-
readResult = await raceReadAgainstDeadline4(reader, timeoutMs);
|
|
30719
|
+
readResult = await raceReadAgainstDeadline4(reader, timeoutMs, opts.signal);
|
|
30332
30720
|
} catch (cause) {
|
|
30333
30721
|
cancelReader();
|
|
30334
30722
|
const aborted = opts.signal?.aborted === true || typeof cause === "object" && cause !== null && cause.name === "AbortError";
|
|
@@ -30343,6 +30731,11 @@ class OpenAiProvider {
|
|
|
30343
30731
|
});
|
|
30344
30732
|
return;
|
|
30345
30733
|
}
|
|
30734
|
+
if (readResult === READ_CANCELLED) {
|
|
30735
|
+
cancelReader();
|
|
30736
|
+
yield errorEvent({ kind: "cancelled", retryable: retryableFor4("cancelled", false), message: "attempt cancelled" });
|
|
30737
|
+
return;
|
|
30738
|
+
}
|
|
30346
30739
|
if (readResult === READ_TIMED_OUT4) {
|
|
30347
30740
|
cancelReader();
|
|
30348
30741
|
yield errorEvent({
|
|
@@ -30471,7 +30864,7 @@ class OpenAiProvider {
|
|
|
30471
30864
|
}
|
|
30472
30865
|
bodies.push({
|
|
30473
30866
|
kind: "reasoning_replay",
|
|
30474
|
-
replay: { providerId:
|
|
30867
|
+
replay: { providerId: this.providerId, kind: "reasoning_item", data: item }
|
|
30475
30868
|
});
|
|
30476
30869
|
}
|
|
30477
30870
|
break;
|
|
@@ -30591,7 +30984,7 @@ class OpenAiProvider {
|
|
|
30591
30984
|
}
|
|
30592
30985
|
}
|
|
30593
30986
|
}
|
|
30594
|
-
var DEFAULT_BASE_URL4 = "https://api.openai.com", PROVIDER_ID2 = "openai", PROVIDER_REVISION4 = "openai-responses-2026-08", DEFAULT_MODEL4, DEFAULT_STREAM_TIMEOUT_MS4 = 120000, READ_TIMED_OUT4;
|
|
30987
|
+
var DEFAULT_BASE_URL4 = "https://api.openai.com", PROVIDER_ID2 = "openai", PROVIDER_REVISION4 = "openai-responses-2026-08", DEFAULT_MODEL4, DEFAULT_STREAM_TIMEOUT_MS4 = 120000, READ_TIMED_OUT4, READ_CANCELLED;
|
|
30595
30988
|
var init_openai_provider = __esm(() => {
|
|
30596
30989
|
init_guard();
|
|
30597
30990
|
init_provider_port();
|
|
@@ -30600,11 +30993,94 @@ var init_openai_provider = __esm(() => {
|
|
|
30600
30993
|
revision: "2026-08"
|
|
30601
30994
|
};
|
|
30602
30995
|
READ_TIMED_OUT4 = Symbol("openai-read-timed-out");
|
|
30996
|
+
READ_CANCELLED = Symbol("openai-read-cancelled");
|
|
30997
|
+
});
|
|
30998
|
+
|
|
30999
|
+
// src/harness/provider/openai-codex/openai-codex-provider.ts
|
|
31000
|
+
class OpenAiCodexProvider {
|
|
31001
|
+
deps;
|
|
31002
|
+
constructor(deps) {
|
|
31003
|
+
this.deps = deps;
|
|
31004
|
+
}
|
|
31005
|
+
describe() {
|
|
31006
|
+
return new OpenAiProvider({ fetch: this.deps.fetch, codex: { accountId: "" } }).describe();
|
|
31007
|
+
}
|
|
31008
|
+
async* stream(request, opts) {
|
|
31009
|
+
const failure = (cancelled) => ({
|
|
31010
|
+
kind: "provider_error",
|
|
31011
|
+
sequence: 0,
|
|
31012
|
+
attemptId: opts.attemptId,
|
|
31013
|
+
error: {
|
|
31014
|
+
kind: cancelled ? "cancelled" : "authentication",
|
|
31015
|
+
retryable: false,
|
|
31016
|
+
message: cancelled ? "attempt cancelled" : `ChatGPT subscription authorization is unavailable. ${LOGIN_HINT}`
|
|
31017
|
+
}
|
|
31018
|
+
});
|
|
31019
|
+
if (isCancelled(opts.signal)) {
|
|
31020
|
+
yield failure(true);
|
|
31021
|
+
return;
|
|
31022
|
+
}
|
|
31023
|
+
if (this.deps.authorize === undefined) {
|
|
31024
|
+
yield failure(false);
|
|
31025
|
+
return;
|
|
31026
|
+
}
|
|
31027
|
+
for (let attempt = 0;attempt < 2; attempt++) {
|
|
31028
|
+
let grant;
|
|
31029
|
+
try {
|
|
31030
|
+
grant = await this.deps.authorize(opts.signal, attempt > 0);
|
|
31031
|
+
} catch {
|
|
31032
|
+
yield failure(isCancelled(opts.signal));
|
|
31033
|
+
return;
|
|
31034
|
+
}
|
|
31035
|
+
if (isCancelled(opts.signal)) {
|
|
31036
|
+
yield failure(true);
|
|
31037
|
+
return;
|
|
31038
|
+
}
|
|
31039
|
+
if (grant.access.length === 0 || grant.accountId.length === 0) {
|
|
31040
|
+
yield failure(false);
|
|
31041
|
+
return;
|
|
31042
|
+
}
|
|
31043
|
+
const provider = new OpenAiProvider({
|
|
31044
|
+
fetch: this.deps.fetch,
|
|
31045
|
+
grant: { network: true, apiKey: grant.access },
|
|
31046
|
+
codex: { accountId: grant.accountId }
|
|
31047
|
+
});
|
|
31048
|
+
let emitted = false;
|
|
31049
|
+
let retry = false;
|
|
31050
|
+
for await (const event of provider.stream(request, opts)) {
|
|
31051
|
+
if (event.error?.kind === "authentication") {
|
|
31052
|
+
if (attempt === 0 && !emitted) {
|
|
31053
|
+
retry = true;
|
|
31054
|
+
break;
|
|
31055
|
+
}
|
|
31056
|
+
yield { ...event, error: { ...event.error, message: `ChatGPT subscription authorization was rejected. ${LOGIN_HINT}` } };
|
|
31057
|
+
return;
|
|
31058
|
+
}
|
|
31059
|
+
emitted = true;
|
|
31060
|
+
yield event;
|
|
31061
|
+
}
|
|
31062
|
+
if (!retry)
|
|
31063
|
+
return;
|
|
31064
|
+
}
|
|
31065
|
+
}
|
|
31066
|
+
}
|
|
31067
|
+
var LOGIN_HINT = "Run `keryx auth login openai-codex`.", isCancelled = (signal) => signal?.aborted === true;
|
|
31068
|
+
var init_openai_codex_provider = __esm(() => {
|
|
31069
|
+
init_openai_provider();
|
|
30603
31070
|
});
|
|
30604
31071
|
|
|
30605
31072
|
// src/harness/provider/make-provider.ts
|
|
30606
31073
|
function makeProvider(name, _model, opts) {
|
|
30607
31074
|
const env = opts.credentials ?? opts.env ?? process.env;
|
|
31075
|
+
if (name === "openai-codex") {
|
|
31076
|
+
const authorize = opts.openAiCodexGrant ?? (opts.credentials === undefined ? (signal, forceRefresh) => ensureOpenAiCodexGrant({
|
|
31077
|
+
fetch: (url, init) => opts.fetch(url, init),
|
|
31078
|
+
...opts.configDir === undefined ? {} : { configDir: opts.configDir },
|
|
31079
|
+
...signal === undefined ? {} : { signal },
|
|
31080
|
+
...forceRefresh === undefined ? {} : { forceRefresh }
|
|
31081
|
+
}) : undefined);
|
|
31082
|
+
return new OpenAiCodexProvider({ fetch: opts.fetch, ...authorize === undefined ? {} : { authorize } });
|
|
31083
|
+
}
|
|
30608
31084
|
if (name === "anthropic") {
|
|
30609
31085
|
const apiKey = env.ANTHROPIC_API_KEY;
|
|
30610
31086
|
if (apiKey === undefined || apiKey.length === 0) {
|
|
@@ -30661,12 +31137,14 @@ var OLLAMA_COMPAT_IDENTITY;
|
|
|
30661
31137
|
var init_make_provider = __esm(() => {
|
|
30662
31138
|
init_providers();
|
|
30663
31139
|
init_catalog();
|
|
31140
|
+
init_openai_subscription();
|
|
30664
31141
|
init_anthropic_provider();
|
|
30665
31142
|
init_openai_compat_provider();
|
|
30666
31143
|
init_fake_provider();
|
|
30667
31144
|
init_gemini_provider();
|
|
30668
31145
|
init_ollama_provider();
|
|
30669
31146
|
init_openai_provider();
|
|
31147
|
+
init_openai_codex_provider();
|
|
30670
31148
|
OLLAMA_COMPAT_IDENTITY = {
|
|
30671
31149
|
defaultBaseUrl: "http://localhost:11434",
|
|
30672
31150
|
providerRevision: "ollama-2024-10-22",
|
|
@@ -36841,7 +37319,7 @@ var init_template_structure = __esm(() => {
|
|
|
36841
37319
|
});
|
|
36842
37320
|
|
|
36843
37321
|
// src/wiki/service.ts
|
|
36844
|
-
import { spawn as
|
|
37322
|
+
import { spawn as spawn4 } from "child_process";
|
|
36845
37323
|
import { createHash as createHash22 } from "crypto";
|
|
36846
37324
|
import { mkdir as mkdir22, readFile as readFile55, readdir as readdir15, rm as rm8, writeFile as writeFile21 } from "fs/promises";
|
|
36847
37325
|
import path118 from "path";
|
|
@@ -37836,7 +38314,7 @@ async function changedModules(cwd, since) {
|
|
|
37836
38314
|
}
|
|
37837
38315
|
function gitDiffNames(cwd, base) {
|
|
37838
38316
|
return new Promise((resolve) => {
|
|
37839
|
-
const child =
|
|
38317
|
+
const child = spawn4("git", ["diff", "--name-only", base], { cwd });
|
|
37840
38318
|
let out = "";
|
|
37841
38319
|
child.stdout.on("data", (chunk) => {
|
|
37842
38320
|
out += String(chunk);
|
|
@@ -39623,7 +40101,7 @@ var init_metaproject_state = __esm(() => {
|
|
|
39623
40101
|
|
|
39624
40102
|
// src/ctx/orient.ts
|
|
39625
40103
|
import path124 from "path";
|
|
39626
|
-
import { spawn as
|
|
40104
|
+
import { spawn as spawn5 } from "child_process";
|
|
39627
40105
|
import { readFile as readFile58 } from "fs/promises";
|
|
39628
40106
|
function metaPath(cwd, parts) {
|
|
39629
40107
|
return path124.join(cwd, ".metaproject", ...parts);
|
|
@@ -39701,7 +40179,7 @@ async function metaprojectIndexContext(cwd) {
|
|
|
39701
40179
|
function gitOutput(cwd, args) {
|
|
39702
40180
|
return new Promise((resolve) => {
|
|
39703
40181
|
try {
|
|
39704
|
-
const child =
|
|
40182
|
+
const child = spawn5("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
39705
40183
|
let out = "";
|
|
39706
40184
|
child.stdout?.on("data", (chunk) => {
|
|
39707
40185
|
out += String(chunk);
|
|
@@ -39864,7 +40342,7 @@ var init_orient = __esm(() => {
|
|
|
39864
40342
|
});
|
|
39865
40343
|
|
|
39866
40344
|
// src/commands/gdgraph.ts
|
|
39867
|
-
import { spawn as
|
|
40345
|
+
import { spawn as spawn6 } from "child_process";
|
|
39868
40346
|
import { existsSync as existsSync19 } from "fs";
|
|
39869
40347
|
import path125 from "path";
|
|
39870
40348
|
async function gdgraphCommand(args) {
|
|
@@ -40506,7 +40984,7 @@ function collectSeeds(args) {
|
|
|
40506
40984
|
async function changedFiles() {
|
|
40507
40985
|
return new Promise((resolve) => {
|
|
40508
40986
|
try {
|
|
40509
|
-
const child =
|
|
40987
|
+
const child = spawn6("git", ["diff", "--name-only", "HEAD"], {
|
|
40510
40988
|
cwd: process.cwd(),
|
|
40511
40989
|
stdio: ["ignore", "pipe", "ignore"]
|
|
40512
40990
|
});
|
|
@@ -40536,7 +41014,7 @@ async function delegateToLocalRunner(args) {
|
|
|
40536
41014
|
return { delegated: false };
|
|
40537
41015
|
}
|
|
40538
41016
|
const exitCode = await new Promise((resolve, reject) => {
|
|
40539
|
-
const child =
|
|
41017
|
+
const child = spawn6(process.execPath, [...SAFE_BUN_SPAWN_ARGS, localRunner, ...args], {
|
|
40540
41018
|
cwd: process.cwd(),
|
|
40541
41019
|
stdio: "inherit",
|
|
40542
41020
|
env: {
|
|
@@ -42464,7 +42942,7 @@ var init_profile = __esm(() => {
|
|
|
42464
42942
|
});
|
|
42465
42943
|
|
|
42466
42944
|
// src/harness/hooks/runner.ts
|
|
42467
|
-
import { spawn as
|
|
42945
|
+
import { spawn as spawn7 } from "child_process";
|
|
42468
42946
|
import path135 from "path";
|
|
42469
42947
|
function buildHookEnv(opts) {
|
|
42470
42948
|
const env = {};
|
|
@@ -42503,7 +42981,7 @@ function spawnAndCollect(cmdPath, args, opts) {
|
|
|
42503
42981
|
const isPosix = process.platform !== "win32";
|
|
42504
42982
|
let child;
|
|
42505
42983
|
try {
|
|
42506
|
-
child =
|
|
42984
|
+
child = spawn7(cmdPath, args, {
|
|
42507
42985
|
cwd: opts.cwd,
|
|
42508
42986
|
env: opts.env,
|
|
42509
42987
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -59309,7 +59787,7 @@ var init_wiki_freshness = () => {};
|
|
|
59309
59787
|
|
|
59310
59788
|
// src/harness/tool/builtin/interactive-tools.ts
|
|
59311
59789
|
import { readdir as readdir28 } from "fs/promises";
|
|
59312
|
-
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "path";
|
|
59790
|
+
import { basename, dirname, isAbsolute, join as join2, relative, resolve as resolve2, sep } from "path";
|
|
59313
59791
|
import { lstatSync as lstatSync4, realpathSync as realpathSync7 } from "fs";
|
|
59314
59792
|
async function readFromLine(file, startLine, cap) {
|
|
59315
59793
|
const reader = file.stream().getReader();
|
|
@@ -59367,7 +59845,7 @@ async function readFromLine(file, startLine, cap) {
|
|
|
59367
59845
|
}
|
|
59368
59846
|
function confineToRoot(root, candidate) {
|
|
59369
59847
|
const rootReal = realpathOr(root);
|
|
59370
|
-
const target =
|
|
59848
|
+
const target = resolve2(rootReal, candidate ?? ".");
|
|
59371
59849
|
const effective = realpathThroughAncestors(target);
|
|
59372
59850
|
if (effective === undefined) {
|
|
59373
59851
|
return null;
|
|
@@ -59385,7 +59863,7 @@ function realpathOr(candidate) {
|
|
|
59385
59863
|
try {
|
|
59386
59864
|
return realpathSync7(candidate);
|
|
59387
59865
|
} catch {
|
|
59388
|
-
return
|
|
59866
|
+
return resolve2(candidate);
|
|
59389
59867
|
}
|
|
59390
59868
|
}
|
|
59391
59869
|
function realpathThroughAncestors(absolute) {
|
|
@@ -59394,7 +59872,7 @@ function realpathThroughAncestors(absolute) {
|
|
|
59394
59872
|
for (;; ) {
|
|
59395
59873
|
try {
|
|
59396
59874
|
const real = realpathSync7(current);
|
|
59397
|
-
return missing.length === 0 ? real :
|
|
59875
|
+
return missing.length === 0 ? real : join2(real, ...missing.reverse());
|
|
59398
59876
|
} catch {
|
|
59399
59877
|
try {
|
|
59400
59878
|
lstatSync4(current);
|
|
@@ -59402,7 +59880,7 @@ function realpathThroughAncestors(absolute) {
|
|
|
59402
59880
|
} catch {}
|
|
59403
59881
|
const parent = dirname(current);
|
|
59404
59882
|
if (parent === current) {
|
|
59405
|
-
return
|
|
59883
|
+
return resolve2(absolute);
|
|
59406
59884
|
}
|
|
59407
59885
|
missing.push(basename(current));
|
|
59408
59886
|
current = parent;
|
|
@@ -65712,7 +66190,7 @@ var init_github = __esm(() => {
|
|
|
65712
66190
|
|
|
65713
66191
|
// src/harness/tool/metaproject-adapter.ts
|
|
65714
66192
|
import { lstat as lstat13, readdir as readdir33, readFile as readFile101, realpath as realpath15, stat as stat19 } from "fs/promises";
|
|
65715
|
-
import { isAbsolute as isAbsolute2, join as
|
|
66193
|
+
import { isAbsolute as isAbsolute2, join as join3, relative as relative2, resolve as resolve3 } from "path";
|
|
65716
66194
|
function isMissingExecutable(cause) {
|
|
65717
66195
|
const message = cause instanceof Error ? cause.message : String(cause);
|
|
65718
66196
|
return /Executable not found|\bENOENT\b|not found in \$?PATH/i.test(message);
|
|
@@ -65753,8 +66231,8 @@ function portableMemoryPath(candidate) {
|
|
|
65753
66231
|
return !isAbsolute2(normalized) && normalized !== ".." && !normalized.startsWith("../") && !normalized.includes("/../") ? normalized : null;
|
|
65754
66232
|
}
|
|
65755
66233
|
function confineToWiki(cwd, candidate) {
|
|
65756
|
-
const wikiRoot =
|
|
65757
|
-
const target =
|
|
66234
|
+
const wikiRoot = join3(cwd, ".metaproject", "wiki");
|
|
66235
|
+
const target = resolve3(wikiRoot, candidate);
|
|
65758
66236
|
const rel = relative2(wikiRoot, target);
|
|
65759
66237
|
if (rel === "") {
|
|
65760
66238
|
return null;
|
|
@@ -65796,7 +66274,7 @@ async function projectRemovalTrail(cwd, query) {
|
|
|
65796
66274
|
async function detectMemoryStoreUnreadable(cwd) {
|
|
65797
66275
|
const root = memoryRoot(cwd);
|
|
65798
66276
|
for (const { folder } of MEMORY_TYPES) {
|
|
65799
|
-
const dir =
|
|
66277
|
+
const dir = join3(root, folder);
|
|
65800
66278
|
try {
|
|
65801
66279
|
await readdir33(dir);
|
|
65802
66280
|
} catch (error) {
|
|
@@ -65810,8 +66288,8 @@ async function detectMemoryStoreUnreadable(cwd) {
|
|
|
65810
66288
|
return null;
|
|
65811
66289
|
}
|
|
65812
66290
|
function confineToSkills(cwd, candidate) {
|
|
65813
|
-
const skillsRoot =
|
|
65814
|
-
const target =
|
|
66291
|
+
const skillsRoot = join3(cwd, ".metaproject", "skills", "gdskills");
|
|
66292
|
+
const target = resolve3(cwd, candidate);
|
|
65815
66293
|
const rel = relative2(skillsRoot, target);
|
|
65816
66294
|
if (rel === "") {
|
|
65817
66295
|
return null;
|
|
@@ -65825,7 +66303,7 @@ async function parseCatalogSummaries(cwd) {
|
|
|
65825
66303
|
const summaries = new Map;
|
|
65826
66304
|
let content;
|
|
65827
66305
|
try {
|
|
65828
|
-
const bytes = await readContainedFile(
|
|
66306
|
+
const bytes = await readContainedFile(join3(cwd, ".metaproject", "skills"), join3(cwd, ".metaproject", "skills", "catalog.md"), { maxBytes: 512 * 1024, requireRegularFile: true });
|
|
65829
66307
|
content = bytes.toString("utf8");
|
|
65830
66308
|
} catch {
|
|
65831
66309
|
return summaries;
|
|
@@ -65846,7 +66324,7 @@ async function parseCatalogSummaries(cwd) {
|
|
|
65846
66324
|
return summaries;
|
|
65847
66325
|
}
|
|
65848
66326
|
async function walkSkillCatalog(cwd) {
|
|
65849
|
-
const root =
|
|
66327
|
+
const root = join3(cwd, ".metaproject", "skills", "gdskills");
|
|
65850
66328
|
const entries = [];
|
|
65851
66329
|
const rootInfo = await lstat13(root).catch(() => null);
|
|
65852
66330
|
if (!rootInfo || !rootInfo.isDirectory() || rootInfo.isSymbolicLink())
|
|
@@ -65864,7 +66342,7 @@ async function walkSkillCatalog(cwd) {
|
|
|
65864
66342
|
for (const categoryDir of categoryDirs) {
|
|
65865
66343
|
if (!categoryDir.isDirectory() && !categoryDir.isSymbolicLink())
|
|
65866
66344
|
continue;
|
|
65867
|
-
const categoryPath =
|
|
66345
|
+
const categoryPath = join3(root, categoryDir.name);
|
|
65868
66346
|
const categoryReal = await realpath15(categoryPath).catch(() => null);
|
|
65869
66347
|
const categoryInfo = await stat19(categoryPath).catch(() => null);
|
|
65870
66348
|
if (!categoryReal || !categoryInfo?.isDirectory() || !isPathInside(rootReal, categoryReal))
|
|
@@ -65876,12 +66354,12 @@ async function walkSkillCatalog(cwd) {
|
|
|
65876
66354
|
continue;
|
|
65877
66355
|
}
|
|
65878
66356
|
for (const skillDir of skillDirs) {
|
|
65879
|
-
const skillPath =
|
|
66357
|
+
const skillPath = join3(categoryPath, skillDir.name);
|
|
65880
66358
|
const skillReal = await realpath15(skillPath).catch(() => null);
|
|
65881
66359
|
const skillInfo = await stat19(skillPath).catch(() => null);
|
|
65882
66360
|
if (!skillReal || !skillInfo?.isDirectory() || !isPathInside(rootReal, skillReal))
|
|
65883
66361
|
continue;
|
|
65884
|
-
const skillMdPath =
|
|
66362
|
+
const skillMdPath = join3(skillPath, "SKILL.md");
|
|
65885
66363
|
let content;
|
|
65886
66364
|
try {
|
|
65887
66365
|
const bytes = await readContainedFile(root, skillMdPath, { maxBytes: 512 * 1024, requireRegularFile: true });
|
|
@@ -66147,7 +66625,7 @@ function createMetaprojectAdapter(cwd, overrides = {}) {
|
|
|
66147
66625
|
exists = false;
|
|
66148
66626
|
}
|
|
66149
66627
|
if (!exists) {
|
|
66150
|
-
const relativePath = toPosix(relative2(
|
|
66628
|
+
const relativePath = toPosix(relative2(join3(cwd, ".metaproject", "wiki"), target));
|
|
66151
66629
|
const registry = await readSectionRegistryState(cwd);
|
|
66152
66630
|
if (registry.state === "unreadable") {
|
|
66153
66631
|
return {
|
|
@@ -66188,7 +66666,7 @@ function createMetaprojectAdapter(cwd, overrides = {}) {
|
|
|
66188
66666
|
};
|
|
66189
66667
|
}
|
|
66190
66668
|
try {
|
|
66191
|
-
const content = (await readContainedFile(
|
|
66669
|
+
const content = (await readContainedFile(join3(cwd, ".metaproject", "wiki"), target, {
|
|
66192
66670
|
maxBytes: 8 * 1024 * 1024,
|
|
66193
66671
|
requireRegularFile: true
|
|
66194
66672
|
})).toString("utf8");
|
|
@@ -66232,7 +66710,7 @@ function createMetaprojectAdapter(cwd, overrides = {}) {
|
|
|
66232
66710
|
}
|
|
66233
66711
|
let hasWikiIndex;
|
|
66234
66712
|
try {
|
|
66235
|
-
await readFile101(
|
|
66713
|
+
await readFile101(join3(cwd, ".metaproject", "wiki", "index.md"), "utf8");
|
|
66236
66714
|
hasWikiIndex = true;
|
|
66237
66715
|
} catch {
|
|
66238
66716
|
hasWikiIndex = false;
|
|
@@ -66507,7 +66985,7 @@ function createMetaprojectAdapter(cwd, overrides = {}) {
|
|
|
66507
66985
|
const byName = catalog.find((entry) => entry.name === input.name);
|
|
66508
66986
|
if (byName !== undefined) {
|
|
66509
66987
|
try {
|
|
66510
|
-
const content = (await readContainedFile(
|
|
66988
|
+
const content = (await readContainedFile(join3(cwd, ".metaproject", "skills", "gdskills"), join3(cwd, byName.path), {
|
|
66511
66989
|
maxBytes: 512 * 1024,
|
|
66512
66990
|
requireRegularFile: true
|
|
66513
66991
|
})).toString("utf8");
|
|
@@ -66520,12 +66998,12 @@ function createMetaprojectAdapter(cwd, overrides = {}) {
|
|
|
66520
66998
|
if (confined === null) {
|
|
66521
66999
|
return { name: input.name, path: "", content: "", found: false };
|
|
66522
67000
|
}
|
|
66523
|
-
const byPath = catalog.find((entry) =>
|
|
67001
|
+
const byPath = catalog.find((entry) => join3(cwd, entry.path) === confined);
|
|
66524
67002
|
if (byPath === undefined) {
|
|
66525
67003
|
return { name: input.name, path: "", content: "", found: false };
|
|
66526
67004
|
}
|
|
66527
67005
|
try {
|
|
66528
|
-
const content = (await readContainedFile(
|
|
67006
|
+
const content = (await readContainedFile(join3(cwd, ".metaproject", "skills", "gdskills"), confined, {
|
|
66529
67007
|
maxBytes: 512 * 1024,
|
|
66530
67008
|
requireRegularFile: true
|
|
66531
67009
|
})).toString("utf8");
|
|
@@ -69660,7 +70138,7 @@ var init_narrate = __esm(() => {
|
|
|
69660
70138
|
// src/commands/memory.ts
|
|
69661
70139
|
import { randomUUID as randomUUID16 } from "crypto";
|
|
69662
70140
|
import { readdir as readdir34 } from "fs/promises";
|
|
69663
|
-
import { join as
|
|
70141
|
+
import { join as join4 } from "path";
|
|
69664
70142
|
function getService() {
|
|
69665
70143
|
service3 ??= createMemoryService();
|
|
69666
70144
|
return service3;
|
|
@@ -69767,7 +70245,7 @@ async function runIndex2(args) {
|
|
|
69767
70245
|
async function detectMemoryStoreUnreadable2(cwd) {
|
|
69768
70246
|
const root = memoryRoot(cwd);
|
|
69769
70247
|
for (const { folder } of MEMORY_TYPES) {
|
|
69770
|
-
const dir =
|
|
70248
|
+
const dir = join4(root, folder);
|
|
69771
70249
|
try {
|
|
69772
70250
|
await readdir34(dir);
|
|
69773
70251
|
} catch (error) {
|
|
@@ -90455,6 +90933,31 @@ var JEV_MODEL_1_13 = "jev-1.13";
|
|
|
90455
90933
|
var DEFAULT_JEV_MODEL = JEV_MODEL_1_13;
|
|
90456
90934
|
var JEV_TOKEN_BUDGET = 64000;
|
|
90457
90935
|
var DEFAULT_JEV_TIMEOUT_MS = 30000;
|
|
90936
|
+
var JEV_MAX_ATTEMPTS = 3;
|
|
90937
|
+
var JEV_RETRY_BACKOFF_MS = [500, 1500];
|
|
90938
|
+
var JEV_RETRY_AFTER_CAP_MS = 5000;
|
|
90939
|
+
var JEV_RETRYABLE_STATUSES = new Set([429, 500, 502, 503, 504]);
|
|
90940
|
+
function defaultJevSleep(ms) {
|
|
90941
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
90942
|
+
}
|
|
90943
|
+
function jevBackoffMs(attemptJustFailed) {
|
|
90944
|
+
return JEV_RETRY_BACKOFF_MS[attemptJustFailed - 1] ?? JEV_RETRY_BACKOFF_MS[JEV_RETRY_BACKOFF_MS.length - 1] ?? 500;
|
|
90945
|
+
}
|
|
90946
|
+
function retryAfterMsFromHeader(headerValue) {
|
|
90947
|
+
if (headerValue === null || headerValue.trim().length === 0)
|
|
90948
|
+
return;
|
|
90949
|
+
const seconds = Number(headerValue);
|
|
90950
|
+
if (Number.isFinite(seconds)) {
|
|
90951
|
+
return seconds > 0 ? Math.min(seconds * 1000, JEV_RETRY_AFTER_CAP_MS) : undefined;
|
|
90952
|
+
}
|
|
90953
|
+
const dateMs = Date.parse(headerValue);
|
|
90954
|
+
if (!Number.isNaN(dateMs)) {
|
|
90955
|
+
const deltaMs = dateMs - Date.now();
|
|
90956
|
+
if (deltaMs > 0)
|
|
90957
|
+
return Math.min(deltaMs, JEV_RETRY_AFTER_CAP_MS);
|
|
90958
|
+
}
|
|
90959
|
+
return;
|
|
90960
|
+
}
|
|
90458
90961
|
|
|
90459
90962
|
class JevCredentialError extends Error {
|
|
90460
90963
|
constructor(message = "OPENROUTER_API_KEY is not set, and no openrouterKey is saved in the keryx shell config: " + "Jev needs an OpenRouter credential and refuses to send an unauthenticated request.") {
|
|
@@ -90478,13 +90981,28 @@ class JevBudgetError extends Error {
|
|
|
90478
90981
|
|
|
90479
90982
|
class JevRequestError extends Error {
|
|
90480
90983
|
status;
|
|
90481
|
-
|
|
90984
|
+
attempts;
|
|
90985
|
+
constructor(status, body, attempts = 1) {
|
|
90482
90986
|
const redactedBody = redactSensitiveText(body);
|
|
90483
|
-
|
|
90987
|
+
const attemptsNote = attempts > 1 ? ` after ${attempts} attempts` : "";
|
|
90988
|
+
super(`Jev request to ${JEV_ENDPOINT} failed: HTTP ${status}${attemptsNote}${redactedBody.length > 0 ? ` \u2014 ${redactedBody.slice(0, 500)}` : ""}`);
|
|
90484
90989
|
this.status = status;
|
|
90990
|
+
this.attempts = attempts;
|
|
90485
90991
|
this.name = "JevRequestError";
|
|
90486
90992
|
}
|
|
90487
90993
|
}
|
|
90994
|
+
|
|
90995
|
+
class JevNetworkError extends Error {
|
|
90996
|
+
attempts;
|
|
90997
|
+
cause;
|
|
90998
|
+
constructor(attempts, cause) {
|
|
90999
|
+
const causeMessage = cause instanceof Error ? cause.message : String(cause);
|
|
91000
|
+
super(`Jev request to ${JEV_ENDPOINT} failed after ${attempts} attempt(s): ${causeMessage}`);
|
|
91001
|
+
this.attempts = attempts;
|
|
91002
|
+
this.cause = cause;
|
|
91003
|
+
this.name = "JevNetworkError";
|
|
91004
|
+
}
|
|
91005
|
+
}
|
|
90488
91006
|
function sourceLabel(source) {
|
|
90489
91007
|
if (source === "env")
|
|
90490
91008
|
return "the OPENROUTER_API_KEY environment variable";
|
|
@@ -90591,22 +91109,9 @@ function validatedAnswer(key, question, raw) {
|
|
|
90591
91109
|
}
|
|
90592
91110
|
return { type: "choice", choice };
|
|
90593
91111
|
}
|
|
90594
|
-
async function
|
|
90595
|
-
const keyResolution = resolveJevApiKeyResolution(opts?.env ?? process.env, opts?.dir);
|
|
90596
|
-
const apiKey = keyResolution.key;
|
|
90597
|
-
if (apiKey === undefined || apiKey.length === 0) {
|
|
90598
|
-
throw new JevCredentialError;
|
|
90599
|
-
}
|
|
90600
|
-
preflightBudget(input.state, input.questions);
|
|
90601
|
-
const body = {
|
|
90602
|
-
model: input.model ?? DEFAULT_JEV_MODEL,
|
|
90603
|
-
state: input.state,
|
|
90604
|
-
questions: input.questions
|
|
90605
|
-
};
|
|
90606
|
-
const timeoutMs = opts?.timeoutMs ?? DEFAULT_JEV_TIMEOUT_MS;
|
|
91112
|
+
async function attemptJevRequest(fetchFn, apiKey, body, timeoutMs, external) {
|
|
90607
91113
|
const controller = new AbortController;
|
|
90608
91114
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
90609
|
-
const external = opts?.signal;
|
|
90610
91115
|
let onExternalAbort;
|
|
90611
91116
|
if (external !== undefined) {
|
|
90612
91117
|
if (external.aborted) {
|
|
@@ -90616,9 +91121,6 @@ async function callJevSystemOne(fetchFn = globalThis.fetch, input, opts) {
|
|
|
90616
91121
|
external.addEventListener("abort", onExternalAbort);
|
|
90617
91122
|
}
|
|
90618
91123
|
}
|
|
90619
|
-
let text;
|
|
90620
|
-
let status;
|
|
90621
|
-
let ok;
|
|
90622
91124
|
try {
|
|
90623
91125
|
const res = await fetchFn(JEV_ENDPOINT, {
|
|
90624
91126
|
method: "POST",
|
|
@@ -90626,25 +91128,65 @@ async function callJevSystemOne(fetchFn = globalThis.fetch, input, opts) {
|
|
|
90626
91128
|
body: JSON.stringify(body),
|
|
90627
91129
|
signal: controller.signal
|
|
90628
91130
|
});
|
|
90629
|
-
|
|
90630
|
-
ok
|
|
90631
|
-
text = await res.text();
|
|
91131
|
+
const text = await res.text();
|
|
91132
|
+
return { kind: "response", status: res.status, ok: res.ok, text, retryAfterHeader: res.headers.get("retry-after") };
|
|
90632
91133
|
} catch (cause) {
|
|
90633
91134
|
if (controller.signal.aborted) {
|
|
90634
|
-
|
|
91135
|
+
return { kind: "timeout" };
|
|
90635
91136
|
}
|
|
90636
|
-
|
|
91137
|
+
return { kind: "network-error", cause };
|
|
90637
91138
|
} finally {
|
|
90638
91139
|
clearTimeout(timer);
|
|
90639
91140
|
if (onExternalAbort !== undefined) {
|
|
90640
91141
|
external?.removeEventListener("abort", onExternalAbort);
|
|
90641
91142
|
}
|
|
90642
91143
|
}
|
|
90643
|
-
|
|
91144
|
+
}
|
|
91145
|
+
async function callJevSystemOne(fetchFn = globalThis.fetch, input, opts) {
|
|
91146
|
+
const keyResolution = resolveJevApiKeyResolution(opts?.env ?? process.env, opts?.dir);
|
|
91147
|
+
const apiKey = keyResolution.key;
|
|
91148
|
+
if (apiKey === undefined || apiKey.length === 0) {
|
|
91149
|
+
throw new JevCredentialError;
|
|
91150
|
+
}
|
|
91151
|
+
preflightBudget(input.state, input.questions);
|
|
91152
|
+
const body = {
|
|
91153
|
+
model: input.model ?? DEFAULT_JEV_MODEL,
|
|
91154
|
+
state: input.state,
|
|
91155
|
+
questions: input.questions
|
|
91156
|
+
};
|
|
91157
|
+
const timeoutMs = opts?.timeoutMs ?? DEFAULT_JEV_TIMEOUT_MS;
|
|
91158
|
+
const external = opts?.signal;
|
|
91159
|
+
const sleepFn = opts?.sleepFn ?? defaultJevSleep;
|
|
91160
|
+
let text;
|
|
91161
|
+
let attempt = 0;
|
|
91162
|
+
for (;; ) {
|
|
91163
|
+
attempt += 1;
|
|
91164
|
+
const outcome = await attemptJevRequest(fetchFn, apiKey, body, timeoutMs, external);
|
|
91165
|
+
if (outcome.kind === "timeout") {
|
|
91166
|
+
throw new JevTimeoutError(timeoutMs);
|
|
91167
|
+
}
|
|
91168
|
+
if (outcome.kind === "network-error") {
|
|
91169
|
+
if (attempt >= JEV_MAX_ATTEMPTS) {
|
|
91170
|
+
throw new JevNetworkError(attempt, outcome.cause);
|
|
91171
|
+
}
|
|
91172
|
+
await sleepFn(jevBackoffMs(attempt));
|
|
91173
|
+
continue;
|
|
91174
|
+
}
|
|
91175
|
+
if (outcome.ok) {
|
|
91176
|
+
text = outcome.text;
|
|
91177
|
+
break;
|
|
91178
|
+
}
|
|
91179
|
+
const status = outcome.status;
|
|
91180
|
+
text = outcome.text;
|
|
91181
|
+
if (JEV_RETRYABLE_STATUSES.has(status) && attempt < JEV_MAX_ATTEMPTS) {
|
|
91182
|
+
const retryAfterMs = retryAfterMsFromHeader(outcome.retryAfterHeader);
|
|
91183
|
+
await sleepFn(retryAfterMs ?? jevBackoffMs(attempt));
|
|
91184
|
+
continue;
|
|
91185
|
+
}
|
|
90644
91186
|
if (status === 401) {
|
|
90645
91187
|
throw new JevAuthRejectedError(keyResolution.source, status, text, looksLikeOpenRouterKey(apiKey));
|
|
90646
91188
|
}
|
|
90647
|
-
throw new JevRequestError(status, text);
|
|
91189
|
+
throw new JevRequestError(status, text, attempt);
|
|
90648
91190
|
}
|
|
90649
91191
|
let parsed;
|
|
90650
91192
|
try {
|
|
@@ -92435,7 +92977,7 @@ init_service4();
|
|
|
92435
92977
|
init_learn();
|
|
92436
92978
|
init_service9();
|
|
92437
92979
|
import { mkdir as mkdir51, readdir as readdir47, readFile as readFile133 } from "fs/promises";
|
|
92438
|
-
import path238, { join as
|
|
92980
|
+
import path238, { join as join9 } from "path";
|
|
92439
92981
|
init_review_learning();
|
|
92440
92982
|
|
|
92441
92983
|
// src/review/managed.ts
|
|
@@ -95480,7 +96022,7 @@ directly.
|
|
|
95480
96022
|
// src/commands/review-jev-rules.ts
|
|
95481
96023
|
init_args();
|
|
95482
96024
|
import { readFile as readFile118, readdir as readdir44, stat as stat22 } from "fs/promises";
|
|
95483
|
-
import { basename as basename2, dirname as dirname2, isAbsolute as isAbsolute3, join as
|
|
96025
|
+
import { basename as basename2, dirname as dirname2, isAbsolute as isAbsolute3, join as join5, relative as relative3 } from "path";
|
|
95484
96026
|
|
|
95485
96027
|
// src/review/conform-state.ts
|
|
95486
96028
|
init_redact();
|
|
@@ -96156,6 +96698,28 @@ function clauseFileKindApplicability(sourceCategory, clauseText, changedFileKind
|
|
|
96156
96698
|
}
|
|
96157
96699
|
return { applicable: true, reason: "hunk is a code file; rule source is not categorised docs" };
|
|
96158
96700
|
}
|
|
96701
|
+
var API_SCOPE_TERMS = ["api", "endpoint", "rest", "http", "openapi"];
|
|
96702
|
+
function isApiScopedRuleSource(source) {
|
|
96703
|
+
const haystack = `${(source.declaredPaths ?? []).join(" ")} ${ruleSourceTitle(source.text)} ${source.path}`.toLowerCase();
|
|
96704
|
+
return API_SCOPE_TERMS.some((term) => new RegExp(`\\b${term}\\b`).test(haystack));
|
|
96705
|
+
}
|
|
96706
|
+
var API_SURFACE_DIR_RE = /(^|\/)(api|routes|controllers|handlers|server)\//i;
|
|
96707
|
+
var HTTP_FRAMEWORK_IMPORT_RE = /\bfrom\s*["'](?:express|fastify|koa|hono|@hono\/[\w-]+|hapi|@nestjs\/(?:common|core)|next\/server|h3)["']/;
|
|
96708
|
+
function looksLikeApiSurfaceHunk(region) {
|
|
96709
|
+
return API_SURFACE_DIR_RE.test(region.path) || HTTP_FRAMEWORK_IMPORT_RE.test(region.text);
|
|
96710
|
+
}
|
|
96711
|
+
function clauseApiScopeApplicability(source, region) {
|
|
96712
|
+
if (!isApiScopedRuleSource(source)) {
|
|
96713
|
+
return { applicable: true, reason: "rule source is not API/endpoint-scoped \u2014 this gate does not apply" };
|
|
96714
|
+
}
|
|
96715
|
+
if (looksLikeApiSurfaceHunk(region)) {
|
|
96716
|
+
return { applicable: true, reason: `rule source is API/endpoint-scoped and ${region.path} looks like an API surface` };
|
|
96717
|
+
}
|
|
96718
|
+
return {
|
|
96719
|
+
applicable: false,
|
|
96720
|
+
reason: `rule source is API/endpoint-scoped (its declared paths/description/title name one of: ${API_SCOPE_TERMS.join(", ")}) but ${region.path} ` + "does not look like an API surface (no api/routes/controllers/handlers/server path segment, and this hunk imports no known HTTP framework)"
|
|
96721
|
+
};
|
|
96722
|
+
}
|
|
96159
96723
|
var DEFAULT_MAX_JEV_RULE_CALLS = 150;
|
|
96160
96724
|
function isHunkCheckableClause(clause) {
|
|
96161
96725
|
return clause.state_kind === "hunk" && clause.checkable;
|
|
@@ -96199,6 +96763,11 @@ function selectRuleHunkPairs(regions, taggedSources, detectedStack, maxCalls = D
|
|
|
96199
96763
|
notApplicable.push({ region, ruleId: source.path, reason: decision.reason });
|
|
96200
96764
|
continue;
|
|
96201
96765
|
}
|
|
96766
|
+
const apiScopeDecision = clauseApiScopeApplicability(source, region);
|
|
96767
|
+
if (!apiScopeDecision.applicable) {
|
|
96768
|
+
notApplicable.push({ region, ruleId: source.path, reason: apiScopeDecision.reason });
|
|
96769
|
+
continue;
|
|
96770
|
+
}
|
|
96202
96771
|
let sawHunkCheckable = false;
|
|
96203
96772
|
let anyFileKindApplicable = false;
|
|
96204
96773
|
for (const clause of clauses) {
|
|
@@ -96620,7 +97189,7 @@ async function walkMatching(root, suffixes) {
|
|
|
96620
97189
|
return;
|
|
96621
97190
|
}
|
|
96622
97191
|
for (const entry of entries) {
|
|
96623
|
-
const full =
|
|
97192
|
+
const full = join5(dir, entry.name);
|
|
96624
97193
|
if (entry.isDirectory()) {
|
|
96625
97194
|
await walk(full);
|
|
96626
97195
|
continue;
|
|
@@ -96645,7 +97214,7 @@ function classify2(file) {
|
|
|
96645
97214
|
}
|
|
96646
97215
|
async function discoverRuleSources(cwd, explicitPaths) {
|
|
96647
97216
|
const discovered = [];
|
|
96648
|
-
for (const root of [
|
|
97217
|
+
for (const root of [join5(cwd, ".metaproject", "rules"), join5(cwd, "rules")]) {
|
|
96649
97218
|
for (const file of await walkMatching(root, [".md", ".mdc"])) {
|
|
96650
97219
|
const text = await readIfExists(file);
|
|
96651
97220
|
if (text === undefined)
|
|
@@ -96654,8 +97223,8 @@ async function discoverRuleSources(cwd, explicitPaths) {
|
|
|
96654
97223
|
}
|
|
96655
97224
|
}
|
|
96656
97225
|
const skillRoots = [
|
|
96657
|
-
[
|
|
96658
|
-
[
|
|
97226
|
+
[join5(cwd, ".metaproject", "project-skills"), "project-skill"],
|
|
97227
|
+
[join5(cwd, ".metaproject", "skills", "gdskills"), "gdskill"]
|
|
96659
97228
|
];
|
|
96660
97229
|
for (const [root, kind] of skillRoots) {
|
|
96661
97230
|
for (const skillMd of await walkMatching(root, ["SKILL.md"])) {
|
|
@@ -96680,7 +97249,7 @@ async function discoverRuleSources(cwd, explicitPaths) {
|
|
|
96680
97249
|
const explicitFiles = [];
|
|
96681
97250
|
const explicitDirSources = [];
|
|
96682
97251
|
for (const raw of explicitPaths) {
|
|
96683
|
-
const resolved = isAbsolute3(raw) ? raw :
|
|
97252
|
+
const resolved = isAbsolute3(raw) ? raw : join5(cwd, raw);
|
|
96684
97253
|
const stats = await stat22(resolved).catch(() => {
|
|
96685
97254
|
return;
|
|
96686
97255
|
});
|
|
@@ -96723,11 +97292,11 @@ async function discoverRuleSources(cwd, explicitPaths) {
|
|
|
96723
97292
|
return { sources: [...byPath.values()].sort((a, b) => a.path.localeCompare(b.path)), excluded: excluded.sort((a, b) => a.path.localeCompare(b.path)) };
|
|
96724
97293
|
}
|
|
96725
97294
|
async function fixturePrPort(dir) {
|
|
96726
|
-
const raw = await readFile118(
|
|
97295
|
+
const raw = await readFile118(join5(dir, "pr.json"), "utf8");
|
|
96727
97296
|
return createFixtureConformPrPort({ pr: JSON.parse(raw) });
|
|
96728
97297
|
}
|
|
96729
97298
|
async function fixtureJevFetch(dir) {
|
|
96730
|
-
const raw = await readFile118(
|
|
97299
|
+
const raw = await readFile118(join5(dir, "jev-responses.json"), "utf8");
|
|
96731
97300
|
const responses = JSON.parse(raw);
|
|
96732
97301
|
let index = 0;
|
|
96733
97302
|
const fn = async () => {
|
|
@@ -97042,6 +97611,10 @@ function touchedExportedSymbols(regionText) {
|
|
|
97042
97611
|
return [...symbols];
|
|
97043
97612
|
}
|
|
97044
97613
|
var TEST_INFIX_RE = /\.(test|spec)\./;
|
|
97614
|
+
var TEST_DIR_RE = /(^|\/)(__tests__|tests?)\//;
|
|
97615
|
+
function isTestFilePath(path) {
|
|
97616
|
+
return TEST_INFIX_RE.test(path) || TEST_DIR_RE.test(path);
|
|
97617
|
+
}
|
|
97045
97618
|
function stemOf(filePath) {
|
|
97046
97619
|
const base = filePath.split("/").at(-1) ?? filePath;
|
|
97047
97620
|
return base.replace(/\.(test|spec)\.[jt]sx?$/, "").replace(/\.[jt]sx?$/, "");
|
|
@@ -97056,7 +97629,7 @@ function testFilesTouchedNearby(regionPath, allChangedFiles) {
|
|
|
97056
97629
|
return allChangedFiles.filter((file) => {
|
|
97057
97630
|
if (file === regionPath)
|
|
97058
97631
|
return false;
|
|
97059
|
-
if (!
|
|
97632
|
+
if (!isTestFilePath(file))
|
|
97060
97633
|
return false;
|
|
97061
97634
|
return stemOf(file) === stem || dirOf(file) === dir;
|
|
97062
97635
|
});
|
|
@@ -97163,7 +97736,10 @@ function batchRiskQuestionsForHunk(facts) {
|
|
|
97163
97736
|
return batches;
|
|
97164
97737
|
}
|
|
97165
97738
|
var NON_CODE_HUNK_RE = /\.(md|txt)$/i;
|
|
97739
|
+
var GENERATED_METADATA_PATH_RE = /(^|\/)\.metaproject\/(flows|data|reviews)\//;
|
|
97166
97740
|
function isNonCodeHunk(path) {
|
|
97741
|
+
if (GENERATED_METADATA_PATH_RE.test(path))
|
|
97742
|
+
return true;
|
|
97167
97743
|
if (/(^|\/)(__fixtures__|fixtures|testdata|__snapshots__|snapshots)\//.test(path))
|
|
97168
97744
|
return false;
|
|
97169
97745
|
return NON_CODE_HUNK_RE.test(path);
|
|
@@ -97237,6 +97813,8 @@ function synthesizeRiskFindings(scored, threshold = DEFAULT_JEV_RISK_THRESHOLD)
|
|
|
97237
97813
|
for (const hunk of rankHunksByRisk(scored)) {
|
|
97238
97814
|
if (hunk.combinedRisk < threshold || hunk.facts.hasNearbyTest)
|
|
97239
97815
|
continue;
|
|
97816
|
+
if (isTestFilePath(hunk.facts.region.path))
|
|
97817
|
+
continue;
|
|
97240
97818
|
const { region } = hunk.facts;
|
|
97241
97819
|
const key = `jev-risk::${region.path}:${region.startLine}-${region.endLine}`;
|
|
97242
97820
|
const severity = hunk.combinedRisk >= 0.85 ? "minor" : "info";
|
|
@@ -97267,6 +97845,8 @@ function riskFindingStats(findings) {
|
|
|
97267
97845
|
function computeRiskRoutingHints(scored, threshold = DEFAULT_JEV_RISK_THRESHOLD) {
|
|
97268
97846
|
const hints = [];
|
|
97269
97847
|
for (const hunk of rankHunksByRisk(scored)) {
|
|
97848
|
+
if (isTestFilePath(hunk.facts.region.path))
|
|
97849
|
+
continue;
|
|
97270
97850
|
const { region } = hunk.facts;
|
|
97271
97851
|
if (hunk.probabilities.security >= threshold) {
|
|
97272
97852
|
hints.push({ file: region.path, startLine: region.startLine, endLine: region.endLine, dimension: "security", probability: hunk.probabilities.security, suggestedReviewer: "review-security-code" });
|
|
@@ -97420,7 +98000,7 @@ async function computeJevRiskResult(options) {
|
|
|
97420
98000
|
topDimension: hunk.topDimension
|
|
97421
98001
|
}));
|
|
97422
98002
|
const status = findings.length > 0 ? "DONE_WITH_CONCERNS" : "DONE";
|
|
97423
|
-
const summary = `Scored ${scored.length} hunk(s) (of ${selection.selected.length + selection.skipped.length} retained) across 5 risk dimensions against ${targetLabel}; ` + `${findings.length} finding(s) at/above threshold ${threshold} with no nearby test. ` + `${selection.skipped.length} hunk(s) skipped by --max-calls ${selection.maxCalls} (${selection.pairsSkipped} pair(s)); ` + `${selection.notCode.length} hunk(s) skipped as not a code hunk (docs/.md/.txt).`;
|
|
98003
|
+
const summary = `Scored ${scored.length} hunk(s) (of ${selection.selected.length + selection.skipped.length} retained) across 5 risk dimensions against ${targetLabel}; ` + `${findings.length} finding(s) at/above threshold ${threshold} with no nearby test. ` + `${selection.skipped.length} hunk(s) skipped by --max-calls ${selection.maxCalls} (${selection.pairsSkipped} pair(s)); ` + `${selection.notCode.length} hunk(s) skipped as not a code hunk (docs/.md/.txt, or .metaproject/{flows,data,reviews} bookkeeping).`;
|
|
97424
98004
|
return {
|
|
97425
98005
|
status,
|
|
97426
98006
|
reviewer: "review-jev-risk",
|
|
@@ -97519,7 +98099,7 @@ async function runJevRisk(args) {
|
|
|
97519
98099
|
// src/commands/review-jev-scenarios.ts
|
|
97520
98100
|
init_args();
|
|
97521
98101
|
import { readFile as readFile122, readdir as readdir45 } from "fs/promises";
|
|
97522
|
-
import { join as
|
|
98102
|
+
import { join as join6, relative as relative4 } from "path";
|
|
97523
98103
|
|
|
97524
98104
|
// src/review/jev-scenarios-config.ts
|
|
97525
98105
|
init_review_gate();
|
|
@@ -97862,7 +98442,7 @@ async function walkMarkdown2(root) {
|
|
|
97862
98442
|
return;
|
|
97863
98443
|
}
|
|
97864
98444
|
for (const entry of entries) {
|
|
97865
|
-
const full =
|
|
98445
|
+
const full = join6(dir, entry.name);
|
|
97866
98446
|
if (entry.isDirectory()) {
|
|
97867
98447
|
await walk(full);
|
|
97868
98448
|
continue;
|
|
@@ -97876,19 +98456,19 @@ async function walkMarkdown2(root) {
|
|
|
97876
98456
|
}
|
|
97877
98457
|
async function discoverScenarioSources(cwd) {
|
|
97878
98458
|
const scenarios = [];
|
|
97879
|
-
for (const file of await walkMarkdown2(
|
|
98459
|
+
for (const file of await walkMarkdown2(join6(cwd, ".metaproject", "wiki", "user-scenarios"))) {
|
|
97880
98460
|
const text = await readIfExists2(file);
|
|
97881
98461
|
if (text === undefined)
|
|
97882
98462
|
continue;
|
|
97883
98463
|
scenarios.push(scenarioFromWikiPage(relative4(cwd, file), text));
|
|
97884
98464
|
}
|
|
97885
|
-
for (const file of await walkMarkdown2(
|
|
98465
|
+
for (const file of await walkMarkdown2(join6(cwd, "docs", "requirements"))) {
|
|
97886
98466
|
const text = await readIfExists2(file);
|
|
97887
98467
|
if (text === undefined)
|
|
97888
98468
|
continue;
|
|
97889
98469
|
scenarios.push(...scenariosFromPrd(relative4(cwd, file), text));
|
|
97890
98470
|
}
|
|
97891
|
-
const readmeLikeFiles = [
|
|
98471
|
+
const readmeLikeFiles = [join6(cwd, "README.md"), ...await walkMarkdown2(join6(cwd, "docs", "docs"))];
|
|
97892
98472
|
for (const file of readmeLikeFiles) {
|
|
97893
98473
|
const text = await readIfExists2(file);
|
|
97894
98474
|
if (text === undefined)
|
|
@@ -97898,11 +98478,11 @@ async function discoverScenarioSources(cwd) {
|
|
|
97898
98478
|
return scenarios;
|
|
97899
98479
|
}
|
|
97900
98480
|
async function fixturePrPort3(dir) {
|
|
97901
|
-
const raw = await readFile122(
|
|
98481
|
+
const raw = await readFile122(join6(dir, "pr.json"), "utf8");
|
|
97902
98482
|
return createFixtureConformPrPort({ pr: JSON.parse(raw) });
|
|
97903
98483
|
}
|
|
97904
98484
|
async function fixtureJevFetch3(dir) {
|
|
97905
|
-
const raw = await readFile122(
|
|
98485
|
+
const raw = await readFile122(join6(dir, "jev-responses.json"), "utf8");
|
|
97906
98486
|
const responses = JSON.parse(raw);
|
|
97907
98487
|
let index = 0;
|
|
97908
98488
|
const fn = async () => {
|
|
@@ -98058,7 +98638,7 @@ async function gitDiffForJevScenarios(ref) {
|
|
|
98058
98638
|
// src/commands/review-jev-docs.ts
|
|
98059
98639
|
init_args();
|
|
98060
98640
|
import { readFile as readFile124, readdir as readdir46 } from "fs/promises";
|
|
98061
|
-
import { join as
|
|
98641
|
+
import { join as join7, relative as relative5 } from "path";
|
|
98062
98642
|
|
|
98063
98643
|
// src/review/jev-docs-config.ts
|
|
98064
98644
|
init_review_gate();
|
|
@@ -98266,7 +98846,6 @@ function boundLinkedSections(linked, maxCalls = DEFAULT_MAX_JEV_DOCS_CALLS, maxP
|
|
|
98266
98846
|
};
|
|
98267
98847
|
}
|
|
98268
98848
|
var JEV_DOCS_TOKEN_BUDGET = 64000;
|
|
98269
|
-
var QUESTIONS_BUDGET_FRACTION4 = 0.5;
|
|
98270
98849
|
var HUNK_TEXT_CHARS = 2000;
|
|
98271
98850
|
var SECTION_TEXT_CHARS = 4000;
|
|
98272
98851
|
function docsQuestionKey(linked) {
|
|
@@ -98300,19 +98879,25 @@ ${bounded}`;
|
|
|
98300
98879
|
].join(`
|
|
98301
98880
|
`);
|
|
98302
98881
|
}
|
|
98882
|
+
function buildDocsBatch(items) {
|
|
98883
|
+
return {
|
|
98884
|
+
items,
|
|
98885
|
+
state: items.map(renderSectionFacts).join(`
|
|
98886
|
+
|
|
98887
|
+
`),
|
|
98888
|
+
questions: Object.fromEntries(items.map((item) => [docsQuestionKey(item), questionFor4(item)]))
|
|
98889
|
+
};
|
|
98890
|
+
}
|
|
98891
|
+
var DOCS_BATCH_BUDGET_FRACTION = 0.4;
|
|
98892
|
+
var MAX_SECTIONS_PER_BATCH = 3;
|
|
98303
98893
|
function batchDocsSections(items) {
|
|
98304
98894
|
const batches = [];
|
|
98305
98895
|
let current = [];
|
|
98896
|
+
const effectiveBudget = JEV_DOCS_TOKEN_BUDGET * DOCS_BATCH_BUDGET_FRACTION;
|
|
98306
98897
|
const flush = () => {
|
|
98307
98898
|
if (current.length === 0)
|
|
98308
98899
|
return;
|
|
98309
|
-
batches.push(
|
|
98310
|
-
items: current,
|
|
98311
|
-
state: current.map(renderSectionFacts).join(`
|
|
98312
|
-
|
|
98313
|
-
`),
|
|
98314
|
-
questions: Object.fromEntries(current.map((item) => [docsQuestionKey(item), questionFor4(item)]))
|
|
98315
|
-
});
|
|
98900
|
+
batches.push(buildDocsBatch(current));
|
|
98316
98901
|
current = [];
|
|
98317
98902
|
};
|
|
98318
98903
|
for (const item of items) {
|
|
@@ -98322,8 +98907,7 @@ function batchDocsSections(items) {
|
|
|
98322
98907
|
`));
|
|
98323
98908
|
const questionsTokens = estimateTokens2(attempt.map((linked) => `${docsQuestionKey(linked)}:noul:${questionFor4(linked).instructions}`).join(`
|
|
98324
98909
|
`));
|
|
98325
|
-
|
|
98326
|
-
if (current.length > 0 && (stateTokens + questionsTokens > JEV_DOCS_TOKEN_BUDGET || !withinQuestionsShare)) {
|
|
98910
|
+
if (current.length > 0 && (attempt.length > MAX_SECTIONS_PER_BATCH || stateTokens + questionsTokens > effectiveBudget)) {
|
|
98327
98911
|
flush();
|
|
98328
98912
|
current = [item];
|
|
98329
98913
|
} else {
|
|
@@ -98472,6 +99056,12 @@ function renderJevDocsMarkdown(result) {
|
|
|
98472
99056
|
}
|
|
98473
99057
|
|
|
98474
99058
|
// src/commands/review-jev-docs.ts
|
|
99059
|
+
function messageOf(error) {
|
|
99060
|
+
return error instanceof Error ? error.message : String(error);
|
|
99061
|
+
}
|
|
99062
|
+
function isMaxTokensExceededError(error) {
|
|
99063
|
+
return error instanceof JevRequestError && error.status === 400 && /max_tokens/i.test(error.message);
|
|
99064
|
+
}
|
|
98475
99065
|
var JEV_DOCS_FLAGS = ["--diff", "--pr", "--max-calls", "--threshold", "--model", "--repo", "--fixtures", "--include", "--json"];
|
|
98476
99066
|
function rejectUnknownJevDocsFlags(args) {
|
|
98477
99067
|
const unknown = args.filter((arg) => arg.startsWith("--") && !JEV_DOCS_FLAGS.includes(arg.split("=")[0]));
|
|
@@ -98528,7 +99118,7 @@ async function walkMatching2(root, suffixes) {
|
|
|
98528
99118
|
return;
|
|
98529
99119
|
}
|
|
98530
99120
|
for (const entry of entries) {
|
|
98531
|
-
const full =
|
|
99121
|
+
const full = join7(dir, entry.name);
|
|
98532
99122
|
if (entry.isDirectory()) {
|
|
98533
99123
|
await walk(full);
|
|
98534
99124
|
continue;
|
|
@@ -98589,7 +99179,7 @@ async function walkAllDocFiles(dir) {
|
|
|
98589
99179
|
return out;
|
|
98590
99180
|
}
|
|
98591
99181
|
for (const entry of entries) {
|
|
98592
|
-
const full =
|
|
99182
|
+
const full = join7(dir, entry.name);
|
|
98593
99183
|
if (entry.isDirectory()) {
|
|
98594
99184
|
if (INCLUDE_WALK_PRUNE_DIRS.has(entry.name))
|
|
98595
99185
|
continue;
|
|
@@ -98603,19 +99193,19 @@ async function walkAllDocFiles(dir) {
|
|
|
98603
99193
|
async function resolveIncludeGlob(cwd, glob) {
|
|
98604
99194
|
const rootDir = includeGlobRootDir(glob);
|
|
98605
99195
|
const regex = includeGlobToRegExp(glob);
|
|
98606
|
-
const candidates = await walkAllDocFiles(
|
|
99196
|
+
const candidates = await walkAllDocFiles(join7(cwd, rootDir));
|
|
98607
99197
|
return candidates.filter((file) => regex.test(relative5(cwd, file)));
|
|
98608
99198
|
}
|
|
98609
99199
|
async function discoverDocFiles(cwd, includeGlobs = []) {
|
|
98610
99200
|
const files = new Set;
|
|
98611
|
-
for (const file of await walkMatching2(
|
|
99201
|
+
for (const file of await walkMatching2(join7(cwd, "docs"), [".md"]))
|
|
98612
99202
|
files.add(file);
|
|
98613
99203
|
for (const name of await matchingFileNames(cwd, /^readme/i)) {
|
|
98614
99204
|
if (/^changelog/i.test(name))
|
|
98615
99205
|
continue;
|
|
98616
|
-
files.add(
|
|
99206
|
+
files.add(join7(cwd, name));
|
|
98617
99207
|
}
|
|
98618
|
-
for (const file of await walkMatching2(
|
|
99208
|
+
for (const file of await walkMatching2(join7(cwd, ".metaproject", "wiki"), [".md"]))
|
|
98619
99209
|
files.add(file);
|
|
98620
99210
|
for (const glob of includeGlobs) {
|
|
98621
99211
|
for (const file of await resolveIncludeGlob(cwd, glob))
|
|
@@ -98643,11 +99233,11 @@ function changedCommandNamesFromRegions(regions) {
|
|
|
98643
99233
|
return names;
|
|
98644
99234
|
}
|
|
98645
99235
|
async function fixturePrPort4(dir) {
|
|
98646
|
-
const raw = await readFile124(
|
|
99236
|
+
const raw = await readFile124(join7(dir, "pr.json"), "utf8");
|
|
98647
99237
|
return createFixtureConformPrPort({ pr: JSON.parse(raw) });
|
|
98648
99238
|
}
|
|
98649
99239
|
async function fixtureJevFetch4(dir) {
|
|
98650
|
-
const raw = await readFile124(
|
|
99240
|
+
const raw = await readFile124(join7(dir, "jev-responses.json"), "utf8");
|
|
98651
99241
|
const responses = JSON.parse(raw);
|
|
98652
99242
|
let index = 0;
|
|
98653
99243
|
const fn = async () => {
|
|
@@ -98681,29 +99271,54 @@ async function computeJevDocsResult(options) {
|
|
|
98681
99271
|
let outputTokens = 0;
|
|
98682
99272
|
let costUsd = 0;
|
|
98683
99273
|
let sawUsage = false;
|
|
98684
|
-
|
|
98685
|
-
|
|
98686
|
-
|
|
98687
|
-
|
|
98688
|
-
|
|
98689
|
-
|
|
98690
|
-
|
|
98691
|
-
|
|
98692
|
-
|
|
98693
|
-
|
|
99274
|
+
let jevError;
|
|
99275
|
+
const attemptBatch = async (batch) => {
|
|
99276
|
+
try {
|
|
99277
|
+
const result = await callJevSystemOne(fetchFn, { model, state: batch.state, questions: batch.questions }, { env: options.env ?? process.env });
|
|
99278
|
+
jevCalls += 1;
|
|
99279
|
+
if (result.usage.input_tokens !== undefined) {
|
|
99280
|
+
inputTokens += result.usage.input_tokens;
|
|
99281
|
+
sawUsage = true;
|
|
99282
|
+
}
|
|
99283
|
+
if (result.usage.output_tokens !== undefined) {
|
|
99284
|
+
outputTokens += result.usage.output_tokens;
|
|
99285
|
+
sawUsage = true;
|
|
99286
|
+
}
|
|
99287
|
+
if (result.usage.cost !== undefined) {
|
|
99288
|
+
costUsd += result.usage.cost;
|
|
99289
|
+
sawUsage = true;
|
|
99290
|
+
}
|
|
99291
|
+
for (const item of batch.items) {
|
|
99292
|
+
const key = `${item.section.file}::${item.section.line}`;
|
|
99293
|
+
const answer = result.answers[key];
|
|
99294
|
+
if (answer === undefined || answer.type !== "noul")
|
|
99295
|
+
continue;
|
|
99296
|
+
const finding = synthesizeDocsFinding(item, answer.noul, threshold);
|
|
99297
|
+
if (finding !== undefined)
|
|
99298
|
+
jevFindings.push(finding);
|
|
99299
|
+
}
|
|
99300
|
+
return { ok: true };
|
|
99301
|
+
} catch (error) {
|
|
99302
|
+
return { ok: false, error };
|
|
98694
99303
|
}
|
|
98695
|
-
|
|
98696
|
-
|
|
98697
|
-
|
|
99304
|
+
};
|
|
99305
|
+
for (const batch of batches) {
|
|
99306
|
+
const attempt = await attemptBatch(batch);
|
|
99307
|
+
if (attempt.ok)
|
|
99308
|
+
continue;
|
|
99309
|
+
if (isMaxTokensExceededError(attempt.error) && batch.items.length > 1) {
|
|
99310
|
+
const mid = Math.ceil(batch.items.length / 2);
|
|
99311
|
+
const halves = [buildDocsBatch(batch.items.slice(0, mid)), buildDocsBatch(batch.items.slice(mid))];
|
|
99312
|
+
for (const half of halves) {
|
|
99313
|
+
const halfAttempt = await attemptBatch(half);
|
|
99314
|
+
if (!halfAttempt.ok && jevError === undefined) {
|
|
99315
|
+
jevError = messageOf(halfAttempt.error);
|
|
99316
|
+
}
|
|
99317
|
+
}
|
|
99318
|
+
continue;
|
|
98698
99319
|
}
|
|
98699
|
-
|
|
98700
|
-
|
|
98701
|
-
const answer = result.answers[key];
|
|
98702
|
-
if (answer === undefined || answer.type !== "noul")
|
|
98703
|
-
continue;
|
|
98704
|
-
const finding = synthesizeDocsFinding(item, answer.noul, threshold);
|
|
98705
|
-
if (finding !== undefined)
|
|
98706
|
-
jevFindings.push(finding);
|
|
99320
|
+
if (jevError === undefined) {
|
|
99321
|
+
jevError = messageOf(attempt.error);
|
|
98707
99322
|
}
|
|
98708
99323
|
}
|
|
98709
99324
|
const findings = [...flagFindings, ...jevFindings].sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
|
|
@@ -98717,6 +99332,7 @@ async function computeJevDocsResult(options) {
|
|
|
98717
99332
|
findings,
|
|
98718
99333
|
stats,
|
|
98719
99334
|
tokens: sawUsage ? { jevCalls, inputTokens, outputTokens, costUsd } : { jevCalls },
|
|
99335
|
+
...jevError !== undefined ? { jevError } : {},
|
|
98720
99336
|
selection: {
|
|
98721
99337
|
maxCalls,
|
|
98722
99338
|
maxPerFile,
|
|
@@ -98795,7 +99411,7 @@ async function runJevDocs(args) {
|
|
|
98795
99411
|
init_args();
|
|
98796
99412
|
init_fs();
|
|
98797
99413
|
import { readFile as readFile126 } from "fs/promises";
|
|
98798
|
-
import { join as
|
|
99414
|
+
import { join as join8 } from "path";
|
|
98799
99415
|
init_pr_comments();
|
|
98800
99416
|
|
|
98801
99417
|
// src/review/jev-comments-config.ts
|
|
@@ -98838,7 +99454,7 @@ function commentFactLines(facts) {
|
|
|
98838
99454
|
];
|
|
98839
99455
|
}
|
|
98840
99456
|
var COMMENT_TOKEN_BUDGET = 64000;
|
|
98841
|
-
var
|
|
99457
|
+
var QUESTIONS_BUDGET_FRACTION4 = 0.5;
|
|
98842
99458
|
var REPLY_BODY_CHARS = 500;
|
|
98843
99459
|
var HUNK_TEXT_CHARS2 = 1500;
|
|
98844
99460
|
var COMMENT_BODY_CHARS = 1500;
|
|
@@ -98904,7 +99520,7 @@ function batchOpenComments(items) {
|
|
|
98904
99520
|
`));
|
|
98905
99521
|
const questionsTokens = estimateTokens2(attempt.map((f) => `${commentQuestionKey(f)}:choice`).join(`
|
|
98906
99522
|
`));
|
|
98907
|
-
const withinQuestionsShare = questionsTokens <= COMMENT_TOKEN_BUDGET *
|
|
99523
|
+
const withinQuestionsShare = questionsTokens <= COMMENT_TOKEN_BUDGET * QUESTIONS_BUDGET_FRACTION4;
|
|
98908
99524
|
if (current.length > 0 && (stateTokens + questionsTokens > COMMENT_TOKEN_BUDGET || !withinQuestionsShare)) {
|
|
98909
99525
|
flush();
|
|
98910
99526
|
current = [item];
|
|
@@ -99089,15 +99705,15 @@ function createFixtureJevCommentsFactsPort(facts) {
|
|
|
99089
99705
|
};
|
|
99090
99706
|
}
|
|
99091
99707
|
async function fixturePrPort5(dir) {
|
|
99092
|
-
const raw = await readFile126(
|
|
99708
|
+
const raw = await readFile126(join8(dir, "pr.json"), "utf8");
|
|
99093
99709
|
return createFixtureConformPrPort({ pr: JSON.parse(raw) });
|
|
99094
99710
|
}
|
|
99095
99711
|
async function fixtureFactsPort(dir) {
|
|
99096
|
-
const raw = await readFile126(
|
|
99712
|
+
const raw = await readFile126(join8(dir, "git-facts.json"), "utf8").catch(() => "{}");
|
|
99097
99713
|
return createFixtureJevCommentsFactsPort(JSON.parse(raw));
|
|
99098
99714
|
}
|
|
99099
99715
|
async function fixtureJevFetch5(dir) {
|
|
99100
|
-
const raw = await readFile126(
|
|
99716
|
+
const raw = await readFile126(join8(dir, "jev-responses.json"), "utf8");
|
|
99101
99717
|
const responses = JSON.parse(raw);
|
|
99102
99718
|
let index = 0;
|
|
99103
99719
|
const fn = async () => {
|
|
@@ -99111,7 +99727,7 @@ async function fixtureJevFetch5(dir) {
|
|
|
99111
99727
|
return fn;
|
|
99112
99728
|
}
|
|
99113
99729
|
function advisoryLabelsPath(cwd, repo, number) {
|
|
99114
|
-
return
|
|
99730
|
+
return join8(cwd, ".metaproject", "data", "review-jev-comments", `labels__${repo.replace(/\//g, "__")}__${number}.json`);
|
|
99115
99731
|
}
|
|
99116
99732
|
async function readCommentAdvisoryLabels(cwd, repo, number) {
|
|
99117
99733
|
const file = advisoryLabelsPath(cwd, repo, number);
|
|
@@ -99651,10 +100267,10 @@ function parseThreshold5(raw) {
|
|
|
99651
100267
|
}
|
|
99652
100268
|
return value;
|
|
99653
100269
|
}
|
|
99654
|
-
function
|
|
100270
|
+
function messageOf2(error) {
|
|
99655
100271
|
return error instanceof Error ? error.message : String(error);
|
|
99656
100272
|
}
|
|
99657
|
-
function
|
|
100273
|
+
function isMaxTokensExceededError2(error) {
|
|
99658
100274
|
return error instanceof JevRequestError && error.status === 400 && /max_tokens/i.test(error.message);
|
|
99659
100275
|
}
|
|
99660
100276
|
async function fixturePrPort6(dir) {
|
|
@@ -99725,19 +100341,19 @@ async function computeJevContractResult(options) {
|
|
|
99725
100341
|
const attempt = await attemptBatch(batch);
|
|
99726
100342
|
if (attempt.ok)
|
|
99727
100343
|
continue;
|
|
99728
|
-
if (
|
|
100344
|
+
if (isMaxTokensExceededError2(attempt.error) && batch.items.length > 1) {
|
|
99729
100345
|
const mid = Math.ceil(batch.items.length / 2);
|
|
99730
100346
|
const halves = [buildContractClaimBatch(batch.items.slice(0, mid)), buildContractClaimBatch(batch.items.slice(mid))];
|
|
99731
100347
|
for (const half of halves) {
|
|
99732
100348
|
const halfAttempt = await attemptBatch(half);
|
|
99733
100349
|
if (!halfAttempt.ok && jevError === undefined) {
|
|
99734
|
-
jevError =
|
|
100350
|
+
jevError = messageOf2(halfAttempt.error);
|
|
99735
100351
|
}
|
|
99736
100352
|
}
|
|
99737
100353
|
continue;
|
|
99738
100354
|
}
|
|
99739
100355
|
if (jevError === undefined) {
|
|
99740
|
-
jevError =
|
|
100356
|
+
jevError = messageOf2(attempt.error);
|
|
99741
100357
|
}
|
|
99742
100358
|
}
|
|
99743
100359
|
for (const item of items) {
|
|
@@ -100149,10 +100765,10 @@ function parseThreshold6(raw) {
|
|
|
100149
100765
|
return value;
|
|
100150
100766
|
}
|
|
100151
100767
|
var LIKELY_DUPLICATE_THRESHOLD = 0.5;
|
|
100152
|
-
function
|
|
100768
|
+
function messageOf3(error) {
|
|
100153
100769
|
return error instanceof Error ? error.message : String(error);
|
|
100154
100770
|
}
|
|
100155
|
-
function
|
|
100771
|
+
function isMaxTokensExceededError3(error) {
|
|
100156
100772
|
return error instanceof JevRequestError && error.status === 400 && /max_tokens/i.test(error.message);
|
|
100157
100773
|
}
|
|
100158
100774
|
async function loadReportRaw(reportPath) {
|
|
@@ -100226,19 +100842,19 @@ async function computeJevTriageResult(options) {
|
|
|
100226
100842
|
const attempt = await attemptBatch(batch);
|
|
100227
100843
|
if (attempt.ok)
|
|
100228
100844
|
continue;
|
|
100229
|
-
if (
|
|
100845
|
+
if (isMaxTokensExceededError3(attempt.error) && batch.items.length > 1) {
|
|
100230
100846
|
const mid = Math.ceil(batch.items.length / 2);
|
|
100231
100847
|
const halves = [buildTriageBatch(batch.items.slice(0, mid)), buildTriageBatch(batch.items.slice(mid))];
|
|
100232
100848
|
for (const half of halves) {
|
|
100233
100849
|
const halfAttempt = await attemptBatch(half);
|
|
100234
100850
|
if (!halfAttempt.ok && jevError === undefined) {
|
|
100235
|
-
jevError =
|
|
100851
|
+
jevError = messageOf3(halfAttempt.error);
|
|
100236
100852
|
}
|
|
100237
100853
|
}
|
|
100238
100854
|
continue;
|
|
100239
100855
|
}
|
|
100240
100856
|
if (jevError === undefined) {
|
|
100241
|
-
jevError =
|
|
100857
|
+
jevError = messageOf3(attempt.error);
|
|
100242
100858
|
}
|
|
100243
100859
|
}
|
|
100244
100860
|
}
|
|
@@ -101421,7 +102037,7 @@ function applyDeterministicOverride(verdict, signals) {
|
|
|
101421
102037
|
// src/review/conform-jev.ts
|
|
101422
102038
|
init_service4();
|
|
101423
102039
|
var CONFORM_TOKEN_BUDGET = 64000;
|
|
101424
|
-
var
|
|
102040
|
+
var QUESTIONS_BUDGET_FRACTION5 = 0.5;
|
|
101425
102041
|
function questionFor6(clause) {
|
|
101426
102042
|
return {
|
|
101427
102043
|
type: "noul",
|
|
@@ -101461,7 +102077,7 @@ function batchConformItems(items, sharedRedactedText) {
|
|
|
101461
102077
|
const factsTokens = estimateTokens2(renderFacts(attempt));
|
|
101462
102078
|
const questionsTokens = estimateQuestionsTokens(attempt);
|
|
101463
102079
|
const total = sharedTokens + factsTokens + questionsTokens;
|
|
101464
|
-
const withinQuestionsShare = questionsTokens <= CONFORM_TOKEN_BUDGET *
|
|
102080
|
+
const withinQuestionsShare = questionsTokens <= CONFORM_TOKEN_BUDGET * QUESTIONS_BUDGET_FRACTION5;
|
|
101465
102081
|
if (current.length > 0 && (total > CONFORM_TOKEN_BUDGET || !withinQuestionsShare)) {
|
|
101466
102082
|
flush();
|
|
101467
102083
|
current = [item];
|
|
@@ -102505,7 +103121,7 @@ async function resolvePort(args) {
|
|
|
102505
103121
|
}
|
|
102506
103122
|
const files = {};
|
|
102507
103123
|
for (const key of ["pull", "pull-comments", "pull-reviews", "issue-comments"]) {
|
|
102508
|
-
const file =
|
|
103124
|
+
const file = join9(fixtures, `${key}.json`);
|
|
102509
103125
|
try {
|
|
102510
103126
|
files[key] = JSON.parse(await readFile133(file, "utf8"));
|
|
102511
103127
|
} catch {
|
|
@@ -102515,13 +103131,13 @@ async function resolvePort(args) {
|
|
|
102515
103131
|
return createFixturePort(files);
|
|
102516
103132
|
}
|
|
102517
103133
|
async function fixtureCiPort(dir) {
|
|
102518
|
-
const runInfo = JSON.parse(await readFile133(
|
|
102519
|
-
const log = await readFile133(
|
|
102520
|
-
const history = await readFile133(
|
|
102521
|
-
const attempts = await readFile133(
|
|
102522
|
-
const changedFiles = await readFile133(
|
|
102523
|
-
const runsByHeadSha = await readFile133(
|
|
102524
|
-
const related = await readFile133(
|
|
103134
|
+
const runInfo = JSON.parse(await readFile133(join9(dir, "ci-run-info.json"), "utf8"));
|
|
103135
|
+
const log = await readFile133(join9(dir, "ci-failed-log.txt"), "utf8").catch(() => "");
|
|
103136
|
+
const history = await readFile133(join9(dir, "ci-history.json"), "utf8").then((raw) => JSON.parse(raw)).catch(() => ({}));
|
|
103137
|
+
const attempts = await readFile133(join9(dir, "ci-attempts.json"), "utf8").then((raw) => JSON.parse(raw)).catch(() => ({}));
|
|
103138
|
+
const changedFiles = await readFile133(join9(dir, "ci-changed-files.json"), "utf8").then((raw) => JSON.parse(raw)).catch(() => ({}));
|
|
103139
|
+
const runsByHeadSha = await readFile133(join9(dir, "ci-runs-by-head-sha.json"), "utf8").then((raw) => JSON.parse(raw)).catch(() => ({}));
|
|
103140
|
+
const related = await readFile133(join9(dir, "ci-related-runs.json"), "utf8").then((raw) => JSON.parse(raw)).catch(() => ({}));
|
|
102525
103141
|
return createFixtureCiPort({
|
|
102526
103142
|
runs: { [runInfo.runId]: runInfo, ...related.runs },
|
|
102527
103143
|
logs: { [runInfo.runId]: log, ...related.logs },
|
|
@@ -102532,7 +103148,7 @@ async function fixtureCiPort(dir) {
|
|
|
102532
103148
|
});
|
|
102533
103149
|
}
|
|
102534
103150
|
async function fixtureJevFetch8(dir) {
|
|
102535
|
-
const body = await readFile133(
|
|
103151
|
+
const body = await readFile133(join9(dir, "jev-response.json"), "utf8");
|
|
102536
103152
|
const fn = async () => new Response(body, { status: 200, headers: { "content-type": "application/json" } });
|
|
102537
103153
|
return fn;
|
|
102538
103154
|
}
|
|
@@ -102698,12 +103314,12 @@ async function runCiTriageEval(cwd, args, evalFile) {
|
|
|
102698
103314
|
}
|
|
102699
103315
|
}
|
|
102700
103316
|
async function fixtureConformJevFetch(dir) {
|
|
102701
|
-
const body = await readFile133(
|
|
103317
|
+
const body = await readFile133(join9(dir, "jev-response.json"), "utf8");
|
|
102702
103318
|
const fn = async () => new Response(body, { status: 200, headers: { "content-type": "application/json" } });
|
|
102703
103319
|
return fn;
|
|
102704
103320
|
}
|
|
102705
103321
|
async function fixtureConformPrPortFrom(dir) {
|
|
102706
|
-
const pr = JSON.parse(await readFile133(
|
|
103322
|
+
const pr = JSON.parse(await readFile133(join9(dir, "pr.json"), "utf8"));
|
|
102707
103323
|
return createFixtureConformPrPort({ pr });
|
|
102708
103324
|
}
|
|
102709
103325
|
function accumulateJevUsage(acc, usage) {
|
|
@@ -102784,7 +103400,7 @@ async function explainConformVerdicts(cwd, refPath, verdicts, threshold, fixture
|
|
|
102784
103400
|
return {};
|
|
102785
103401
|
if (fixturesDir !== undefined) {
|
|
102786
103402
|
try {
|
|
102787
|
-
return JSON.parse(await readFile133(
|
|
103403
|
+
return JSON.parse(await readFile133(join9(fixturesDir, "explain-response.json"), "utf8"));
|
|
102788
103404
|
} catch {
|
|
102789
103405
|
return {};
|
|
102790
103406
|
}
|
|
@@ -102832,7 +103448,7 @@ function parsePositiveIntFlag(args, flag, fallback) {
|
|
|
102832
103448
|
}
|
|
102833
103449
|
async function readReportFindings(reportDir) {
|
|
102834
103450
|
try {
|
|
102835
|
-
const raw = JSON.parse(await readFile133(
|
|
103451
|
+
const raw = JSON.parse(await readFile133(join9(reportDir, "findings.json"), "utf8"));
|
|
102836
103452
|
return Array.isArray(raw) ? raw : [];
|
|
102837
103453
|
} catch {
|
|
102838
103454
|
return [];
|
|
@@ -102904,7 +103520,7 @@ async function runConform(args) {
|
|
|
102904
103520
|
const fetchFn = fixturesDir === undefined ? globalThis.fetch : await fixtureConformJevFetch(fixturesDir);
|
|
102905
103521
|
const acc = { jevCalls: 0, inputTokens: 0, outputTokens: 0, costUsd: 0, sawUsage: false };
|
|
102906
103522
|
const clauses = await resolveConformClauseTags(cwd, refPath, docText, rawClauses, fetchFn, model, acc);
|
|
102907
|
-
await writeFileAtomic(
|
|
103523
|
+
await writeFileAtomic(join9(cwd, CONFORM_RECENTS_PATH), `${JSON.stringify(withRecentDoc(await readRecentConformDocs(cwd), refPath), null, 2)}
|
|
102908
103524
|
`, { mode: 384 }).catch(() => {});
|
|
102909
103525
|
const notCheckable = clauses.filter((c) => !c.checkable);
|
|
102910
103526
|
const checkable = clauses.filter((c) => c.checkable);
|
|
@@ -102936,7 +103552,7 @@ async function runConform(args) {
|
|
|
102936
103552
|
hunkTotalRegions = hunkResult.totalRegions;
|
|
102937
103553
|
} else if (reportDir !== undefined) {
|
|
102938
103554
|
target = { kind: "report", label: reportDir };
|
|
102939
|
-
const reportMarkdown = await readFile133(
|
|
103555
|
+
const reportMarkdown = await readFile133(join9(reportDir, "report.md"), "utf8").catch(() => "");
|
|
102940
103556
|
const findings = await readReportFindings(reportDir);
|
|
102941
103557
|
const facts = computeReportConformFacts(reportMarkdown, findings);
|
|
102942
103558
|
const redacted = reportRedactedStateText(reportMarkdown);
|
|
@@ -102977,7 +103593,7 @@ async function runConform(args) {
|
|
|
102977
103593
|
}
|
|
102978
103594
|
async function readRecentConformDocs(cwd) {
|
|
102979
103595
|
try {
|
|
102980
|
-
const raw = JSON.parse(await readFile133(
|
|
103596
|
+
const raw = JSON.parse(await readFile133(join9(cwd, CONFORM_RECENTS_PATH), "utf8"));
|
|
102981
103597
|
return Array.isArray(raw) ? raw : [];
|
|
102982
103598
|
} catch {
|
|
102983
103599
|
return [];
|
|
@@ -103223,7 +103839,7 @@ async function runStack(args) {
|
|
|
103223
103839
|
console.log(renderStackScopingMarkdown(detected, decisions));
|
|
103224
103840
|
}
|
|
103225
103841
|
async function stackScopingForInstalledReviewers(cwd, detected) {
|
|
103226
|
-
const reviewRoot =
|
|
103842
|
+
const reviewRoot = join9(cwd, ".metaproject", "skills", "gdskills", "review");
|
|
103227
103843
|
let entries;
|
|
103228
103844
|
try {
|
|
103229
103845
|
entries = await readdir47(reviewRoot, { withFileTypes: true });
|
|
@@ -103235,7 +103851,7 @@ async function stackScopingForInstalledReviewers(cwd, detected) {
|
|
|
103235
103851
|
if (!entry.isDirectory()) {
|
|
103236
103852
|
continue;
|
|
103237
103853
|
}
|
|
103238
|
-
const skillMdPath =
|
|
103854
|
+
const skillMdPath = join9(reviewRoot, entry.name, "SKILL.md");
|
|
103239
103855
|
let content;
|
|
103240
103856
|
try {
|
|
103241
103857
|
content = await readFile133(skillMdPath, "utf8");
|
|
@@ -110205,7 +110821,7 @@ async function diagnose(server, options) {
|
|
|
110205
110821
|
skipped: catalog.skipped
|
|
110206
110822
|
};
|
|
110207
110823
|
} catch (error) {
|
|
110208
|
-
return { ...base, status: "failed", toolCount: 0, skipped: [], detail:
|
|
110824
|
+
return { ...base, status: "failed", toolCount: 0, skipped: [], detail: messageOf4(error) };
|
|
110209
110825
|
} finally {
|
|
110210
110826
|
try {
|
|
110211
110827
|
await connection.close();
|
|
@@ -110227,7 +110843,7 @@ function oauthErrorCodeOf(error) {
|
|
|
110227
110843
|
return typeof code === "string" ? code : "";
|
|
110228
110844
|
}
|
|
110229
110845
|
function explainConnectFailure(transport, server, error) {
|
|
110230
|
-
const message = sanitiseForDisplay(
|
|
110846
|
+
const message = sanitiseForDisplay(messageOf4(error));
|
|
110231
110847
|
if (transport !== "http")
|
|
110232
110848
|
return message;
|
|
110233
110849
|
const url = displayUrl(server.raw.url);
|
|
@@ -110300,7 +110916,7 @@ function syscallCodeOf(error) {
|
|
|
110300
110916
|
}
|
|
110301
110917
|
return;
|
|
110302
110918
|
}
|
|
110303
|
-
function
|
|
110919
|
+
function messageOf4(error) {
|
|
110304
110920
|
return error instanceof Error ? error.message : String(error);
|
|
110305
110921
|
}
|
|
110306
110922
|
async function withTimeout(work, ms, name) {
|
|
@@ -110350,38 +110966,8 @@ function formatDoctorReport(report) {
|
|
|
110350
110966
|
`);
|
|
110351
110967
|
}
|
|
110352
110968
|
|
|
110353
|
-
// src/
|
|
110354
|
-
|
|
110355
|
-
function browserOpenPlan(url, platform = process.platform, env = process.env) {
|
|
110356
|
-
if (url.length === 0) {
|
|
110357
|
-
return;
|
|
110358
|
-
}
|
|
110359
|
-
if (platform === "darwin") {
|
|
110360
|
-
return { cmd: "open", args: [url] };
|
|
110361
|
-
}
|
|
110362
|
-
if (platform === "win32") {
|
|
110363
|
-
return { cmd: "cmd", args: ["/c", "start", "", url] };
|
|
110364
|
-
}
|
|
110365
|
-
if (typeof env.DISPLAY === "string" && env.DISPLAY.length > 0) {
|
|
110366
|
-
return { cmd: "xdg-open", args: [url] };
|
|
110367
|
-
}
|
|
110368
|
-
if (typeof env.WAYLAND_DISPLAY === "string" && env.WAYLAND_DISPLAY.length > 0) {
|
|
110369
|
-
return { cmd: "xdg-open", args: [url] };
|
|
110370
|
-
}
|
|
110371
|
-
return;
|
|
110372
|
-
}
|
|
110373
|
-
function openVerificationUrl(url, deps = {}) {
|
|
110374
|
-
const plan = browserOpenPlan(url, deps.platform ?? process.platform, deps.env ?? process.env);
|
|
110375
|
-
if (plan === undefined) {
|
|
110376
|
-
return;
|
|
110377
|
-
}
|
|
110378
|
-
const spawnFn = deps.spawn ?? spawn7;
|
|
110379
|
-
try {
|
|
110380
|
-
const child = spawnFn(plan.cmd, plan.args, { stdio: "ignore", detached: true });
|
|
110381
|
-
child.on("error", () => {});
|
|
110382
|
-
child.unref();
|
|
110383
|
-
} catch {}
|
|
110384
|
-
}
|
|
110969
|
+
// src/commands/mcp-servers.ts
|
|
110970
|
+
init_open_url();
|
|
110385
110971
|
|
|
110386
110972
|
// src/mcp-servers/oauth-callback.ts
|
|
110387
110973
|
import { randomBytes as randomBytes8, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
@@ -126604,7 +127190,7 @@ init_safe_exec();
|
|
|
126604
127190
|
init_metaproject_operations();
|
|
126605
127191
|
init_interactive_tools();
|
|
126606
127192
|
import { existsSync as existsSync43 } from "fs";
|
|
126607
|
-
import { resolve as
|
|
127193
|
+
import { resolve as resolve4 } from "path";
|
|
126608
127194
|
var MAX_OUTPUT_BYTES3 = 20000;
|
|
126609
127195
|
var RG_UNAVAILABLE_SIGNATURE = /ripgrep \(rg\) is not installed|Executable not found[^\n]*\brg\b|\brg\b[^\n]*\bENOENT\b|\bENOENT\b[^\n]*\brg\b/i;
|
|
126610
127196
|
var SEARCH_CODE_RG_UNAVAILABLE_MESSAGE = "ripgrep (rg) is not installed or not on PATH, and search_code needs it. Install it " + "(`brew install ripgrep` / `apt install ripgrep`), or use read_file and list_dir to " + "inspect files directly instead of retrying search_code.";
|
|
@@ -126615,11 +127201,11 @@ function normalizeSearchResult(result) {
|
|
|
126615
127201
|
return result;
|
|
126616
127202
|
}
|
|
126617
127203
|
var KERYX_ENTRIES = [
|
|
126618
|
-
|
|
126619
|
-
|
|
127204
|
+
resolve4(import.meta.dir, "..", "..", "..", "cli.ts"),
|
|
127205
|
+
resolve4(import.meta.dir, "cli.js")
|
|
126620
127206
|
];
|
|
126621
127207
|
function keryxSelfCommand(main = Bun.main, execPath = process.execPath, exists = existsSync43, entries = KERYX_ENTRIES) {
|
|
126622
|
-
if (entries.includes(
|
|
127208
|
+
if (entries.includes(resolve4(main)) && exists(main)) {
|
|
126623
127209
|
return [execPath, ...SAFE_BUN_SPAWN_ARGS, main];
|
|
126624
127210
|
}
|
|
126625
127211
|
if (isCompiledBinaryEntry(main)) {
|
|
@@ -132751,6 +133337,7 @@ ${lines.join(`
|
|
|
132751
133337
|
}
|
|
132752
133338
|
|
|
132753
133339
|
// src/tui/tui-shell.ts
|
|
133340
|
+
init_subscription_models();
|
|
132754
133341
|
init_agent();
|
|
132755
133342
|
init_single_turn();
|
|
132756
133343
|
|
|
@@ -146411,6 +146998,7 @@ init_patch_risk();
|
|
|
146411
146998
|
init_catalog();
|
|
146412
146999
|
init_grants();
|
|
146413
147000
|
init_login();
|
|
147001
|
+
init_open_url();
|
|
146414
147002
|
init_shell_config();
|
|
146415
147003
|
init_provider_config();
|
|
146416
147004
|
|
|
@@ -151780,14 +152368,22 @@ async function filterConnectedDetectedProviders(detected, options = {}) {
|
|
|
151780
152368
|
const env = options.env ?? process.env;
|
|
151781
152369
|
const connected = [];
|
|
151782
152370
|
for (const prov of detected) {
|
|
151783
|
-
|
|
152371
|
+
if (prov.name === "openai-codex") {
|
|
152372
|
+
if (oauthAccessToken(prov.name, options.configDir) === undefined)
|
|
152373
|
+
continue;
|
|
152374
|
+
const result = await fetchOpenAiCodexModels(fetchFn, { ...options.configDir !== undefined ? { configDir: options.configDir } : {} });
|
|
152375
|
+
if (result.source === "live" && result.models.length > 0)
|
|
152376
|
+
connected.push({ ...prov, models: result.models });
|
|
152377
|
+
continue;
|
|
152378
|
+
}
|
|
152379
|
+
const registry = providerByName(prov.name, options.configDir);
|
|
151784
152380
|
if (registry === undefined) {
|
|
151785
152381
|
connected.push(prov);
|
|
151786
152382
|
continue;
|
|
151787
152383
|
}
|
|
151788
152384
|
const requiresApiKey = registry.requiresApiKey ?? true;
|
|
151789
152385
|
const envKey = prov.envKey ?? registry.envKey;
|
|
151790
|
-
const raw = envKey !== undefined ? env[envKey] : undefined;
|
|
152386
|
+
const raw = envKey !== undefined ? env[envKey] ?? loadShellConfig(options.configDir).apiKeys?.[envKey] : undefined;
|
|
151791
152387
|
if (requiresApiKey && (raw === undefined || raw.length === 0)) {
|
|
151792
152388
|
continue;
|
|
151793
152389
|
}
|
|
@@ -152872,7 +153468,7 @@ function pickAuthMethodStep(otui, target, providerLabel, methods) {
|
|
|
152872
153468
|
});
|
|
152873
153469
|
});
|
|
152874
153470
|
}
|
|
152875
|
-
function runDeviceLoginInTui(otui, target, provider, dir) {
|
|
153471
|
+
function runDeviceLoginInTui(otui, target, provider, dir, fetchFn = globalThis.fetch) {
|
|
152876
153472
|
const r = stepRenderer(target);
|
|
152877
153473
|
return new Promise((resolve) => {
|
|
152878
153474
|
const controller = new AbortController;
|
|
@@ -152895,7 +153491,7 @@ function runDeviceLoginInTui(otui, target, provider, dir) {
|
|
|
152895
153491
|
};
|
|
152896
153492
|
loginDeviceCode({
|
|
152897
153493
|
provider,
|
|
152898
|
-
fetch: (input, init) =>
|
|
153494
|
+
fetch: (input, init) => fetchFn(input, init),
|
|
152899
153495
|
signal: controller.signal,
|
|
152900
153496
|
...dir !== undefined ? { dir } : {},
|
|
152901
153497
|
onChallenge: (challenge) => {
|
|
@@ -152908,7 +153504,7 @@ function runDeviceLoginInTui(otui, target, provider, dir) {
|
|
|
152908
153504
|
}
|
|
152909
153505
|
if (result.ok) {
|
|
152910
153506
|
cleanup();
|
|
152911
|
-
applyOAuthAccessToEnv();
|
|
153507
|
+
applyOAuthAccessToEnv(dir);
|
|
152912
153508
|
resolve(true);
|
|
152913
153509
|
return;
|
|
152914
153510
|
}
|
|
@@ -152923,7 +153519,7 @@ function runDeviceLoginInTui(otui, target, provider, dir) {
|
|
|
152923
153519
|
});
|
|
152924
153520
|
}
|
|
152925
153521
|
async function modelsForPicker(prov, deps = {}) {
|
|
152926
|
-
return await resolveModelsForPicker(deps.fetch ?? globalThis.fetch, prov, deps.env ?? process.env);
|
|
153522
|
+
return await resolveModelsForPicker(deps.fetch ?? globalThis.fetch, prov, deps.env ?? process.env, { ...deps.configDir !== undefined ? { configDir: deps.configDir } : {} });
|
|
152927
153523
|
}
|
|
152928
153524
|
function endpointMayBeAtFault(failure) {
|
|
152929
153525
|
return failure?.kind === "unreachable" || failure?.kind === "http";
|
|
@@ -153067,7 +153663,7 @@ function pickConnectedProviderStep(otui, target, detected, opts) {
|
|
|
153067
153663
|
}
|
|
153068
153664
|
async function runTest(name) {
|
|
153069
153665
|
const block = rowBlocks.find((b) => b.name === name);
|
|
153070
|
-
const provider =
|
|
153666
|
+
const provider = connectionProviderByName(name, opts.configDir);
|
|
153071
153667
|
if (block === undefined)
|
|
153072
153668
|
return;
|
|
153073
153669
|
if (provider === undefined) {
|
|
@@ -153231,7 +153827,8 @@ function selectProviderModelInTui(otui, rOrChrome, detected, options = {}) {
|
|
|
153231
153827
|
}
|
|
153232
153828
|
const candidatesPromise = options.onlyConnected ? filterConnectedDetectedProviders(detected, {
|
|
153233
153829
|
...options.fetch !== undefined ? { fetch: options.fetch } : {},
|
|
153234
|
-
...options.env !== undefined ? { env: options.env } : {}
|
|
153830
|
+
...options.env !== undefined ? { env: options.env } : {},
|
|
153831
|
+
...options.configDir !== undefined ? { configDir: options.configDir } : {}
|
|
153235
153832
|
}) : Promise.resolve(detected);
|
|
153236
153833
|
(async () => {
|
|
153237
153834
|
const candidates = await candidatesPromise;
|
|
@@ -153275,7 +153872,7 @@ function selectProviderModelInTui(otui, rOrChrome, detected, options = {}) {
|
|
|
153275
153872
|
}
|
|
153276
153873
|
continue;
|
|
153277
153874
|
}
|
|
153278
|
-
const lockDiscoveredHost =
|
|
153875
|
+
const lockDiscoveredHost = ["github-copilot", "openai", "openai-codex"].includes(prov.name);
|
|
153279
153876
|
let selectedBaseUrl = options.onlyConnected || prov.baseUrl === undefined || lockDiscoveredHost ? prov.baseUrl : await promptBaseUrlStep(otui, rOrChrome, prov.label ?? prov.name, prov.baseUrl);
|
|
153280
153877
|
if (!options.onlyConnected && !lockDiscoveredHost && prov.baseUrl !== undefined && selectedBaseUrl === undefined) {
|
|
153281
153878
|
continue;
|
|
@@ -153287,12 +153884,12 @@ function selectProviderModelInTui(otui, rOrChrome, detected, options = {}) {
|
|
|
153287
153884
|
const envKey = prov.envKey;
|
|
153288
153885
|
const label = prov.label ?? prov.name;
|
|
153289
153886
|
const collectCredential = async (notice) => {
|
|
153290
|
-
if (options.onlyConnected || envKey === undefined) {
|
|
153887
|
+
if (options.onlyConnected || envKey === undefined && !catalogAllows(prov.name, "device-code")) {
|
|
153291
153888
|
return "ok";
|
|
153292
153889
|
}
|
|
153293
153890
|
if (notice === undefined) {
|
|
153294
|
-
const existingKey = (options.env ?? process.env)[envKey];
|
|
153295
|
-
const hasOauth = oauthAccessToken(prov.name, options.configDir) !== undefined;
|
|
153891
|
+
const existingKey = envKey === undefined ? undefined : (options.env ?? process.env)[envKey] ?? loadShellConfig(options.configDir).apiKeys?.[envKey];
|
|
153892
|
+
const hasOauth = prov.name !== "openai" && oauthAccessToken(prov.name, options.configDir) !== undefined;
|
|
153296
153893
|
if (existingKey !== undefined && existingKey.length > 0 || hasOauth) {
|
|
153297
153894
|
return "ok";
|
|
153298
153895
|
}
|
|
@@ -153310,7 +153907,7 @@ function selectProviderModelInTui(otui, rOrChrome, detected, options = {}) {
|
|
|
153310
153907
|
}
|
|
153311
153908
|
}
|
|
153312
153909
|
if (method === "device-code") {
|
|
153313
|
-
if (!await runDeviceLoginInTui(otui, rOrChrome, prov.name, options.configDir)) {
|
|
153910
|
+
if (!await runDeviceLoginInTui(otui, rOrChrome, prov.name, options.configDir, options.fetch)) {
|
|
153314
153911
|
return "back";
|
|
153315
153912
|
}
|
|
153316
153913
|
const saved = loadShellConfig(options.configDir).baseUrls?.[prov.name];
|
|
@@ -153320,8 +153917,10 @@ function selectProviderModelInTui(otui, rOrChrome, detected, options = {}) {
|
|
|
153320
153917
|
}
|
|
153321
153918
|
return "ok";
|
|
153322
153919
|
}
|
|
153920
|
+
if (envKey === undefined)
|
|
153921
|
+
return "back";
|
|
153323
153922
|
const kr = await promptApiKeyStep(otui, rOrChrome, {
|
|
153324
|
-
label,
|
|
153923
|
+
label: prov.name === "openai" ? "OpenAI" : label,
|
|
153325
153924
|
envKey,
|
|
153326
153925
|
...notice === undefined ? {} : { notice }
|
|
153327
153926
|
});
|
|
@@ -153340,7 +153939,8 @@ function selectProviderModelInTui(otui, rOrChrome, detected, options = {}) {
|
|
|
153340
153939
|
}
|
|
153341
153940
|
const modelDeps = {
|
|
153342
153941
|
...options.fetch === undefined ? {} : { fetch: options.fetch },
|
|
153343
|
-
...options.env === undefined ? {} : { env: options.env }
|
|
153942
|
+
...options.env === undefined ? {} : { env: options.env },
|
|
153943
|
+
...options.configDir === undefined ? {} : { configDir: options.configDir }
|
|
153344
153944
|
};
|
|
153345
153945
|
let models = await modelsForPicker(selectedProvider, modelDeps);
|
|
153346
153946
|
if (models.failure?.kind === "rejected") {
|
|
@@ -153349,7 +153949,7 @@ function selectProviderModelInTui(otui, rOrChrome, detected, options = {}) {
|
|
|
153349
153949
|
}
|
|
153350
153950
|
models = await modelsForPicker(selectedProvider, modelDeps);
|
|
153351
153951
|
}
|
|
153352
|
-
if (!options.onlyConnected && selectedProvider.baseUrl !== undefined && endpointMayBeAtFault(models.failure)) {
|
|
153952
|
+
if (!options.onlyConnected && !lockDiscoveredHost && selectedProvider.baseUrl !== undefined && endpointMayBeAtFault(models.failure)) {
|
|
153353
153953
|
const corrected = await promptBaseUrlStep(otui, rOrChrome, label, selectedProvider.baseUrl, modelsFailureLine(label, models.failure));
|
|
153354
153954
|
if (corrected === undefined) {
|
|
153355
153955
|
continue;
|
|
@@ -156474,7 +157074,7 @@ Usage: /reasoning <${REASONING_EFFORT_LEVELS.join("|")}>
|
|
|
156474
157074
|
io.onSystem?.(`Reasoning effort: ${wanted}
|
|
156475
157075
|
`);
|
|
156476
157076
|
const compatProvider = providerByName(currentSel.provider);
|
|
156477
|
-
if (compatProvider !== undefined && compatProvider.reasoning === undefined) {
|
|
157077
|
+
if (currentSel.provider !== "openai" && compatProvider !== undefined && compatProvider.reasoning === undefined) {
|
|
156478
157078
|
io.onSystem?.(`Note: ${currentSel.provider} is OpenAI-compatible with no "reasoning" entry \u2014 its reasoning ` + `is configured per-provider in llm-providers.json (reasoning.requestParams); this setting has no effect for it.
|
|
156479
157079
|
`);
|
|
156480
157080
|
}
|
|
@@ -156537,7 +157137,8 @@ Usage: /reasoning <${REASONING_EFFORT_LEVELS.join("|")}>
|
|
|
156537
157137
|
`);
|
|
156538
157138
|
}
|
|
156539
157139
|
if (name === currentSel.provider) {
|
|
156540
|
-
io.onSystem?.(`\u25C7 ${name} disconnected \u2014
|
|
157140
|
+
io.onSystem?.(name === "openai-codex" ? `\u25C7 ${name} disconnected \u2014 it stays selected, but the next turn requires a new login. Use /provider to reconnect or /connect to switch.
|
|
157141
|
+
` : `\u25C7 ${name} disconnected \u2014 this session keeps its already-loaded credential until you /connect another provider or restart.
|
|
156541
157142
|
`);
|
|
156542
157143
|
}
|
|
156543
157144
|
}
|
|
@@ -157880,6 +158481,17 @@ Starting a new session.
|
|
|
157880
158481
|
modelParams = picked.modelParams ?? {};
|
|
157881
158482
|
provider = makeActive();
|
|
157882
158483
|
};
|
|
158484
|
+
const selectAndApply = async (options) => {
|
|
158485
|
+
if (deps.selectProviderModel === undefined)
|
|
158486
|
+
return;
|
|
158487
|
+
try {
|
|
158488
|
+
applySelection(await deps.selectProviderModel(io, options));
|
|
158489
|
+
} catch (cause) {
|
|
158490
|
+
system(`${cause instanceof Error ? cause.message : "Provider selection failed"}
|
|
158491
|
+
Kept the current session.
|
|
158492
|
+
`);
|
|
158493
|
+
}
|
|
158494
|
+
};
|
|
157883
158495
|
for await (const line of io.lines) {
|
|
157884
158496
|
io.onSafeBoundary?.();
|
|
157885
158497
|
if (line.startsWith("/")) {
|
|
@@ -158017,12 +158629,19 @@ Kept the current session.
|
|
|
158017
158629
|
`);
|
|
158018
158630
|
continue;
|
|
158019
158631
|
}
|
|
158020
|
-
|
|
158021
|
-
applySelection(picked);
|
|
158632
|
+
await selectAndApply({ onlyProvider: providerName });
|
|
158022
158633
|
continue;
|
|
158023
158634
|
}
|
|
158024
158635
|
if (command === "/provider") {
|
|
158025
158636
|
if (argument.length > 0) {
|
|
158637
|
+
if (argument === "openai-codex") {
|
|
158638
|
+
if (deps.selectProviderModel === undefined) {
|
|
158639
|
+
system("Interactive subscription selection is not available. Run `keryx auth login openai-codex` and select an authorized model.\n");
|
|
158640
|
+
} else {
|
|
158641
|
+
await selectAndApply({ onlyProvider: "openai-codex" });
|
|
158642
|
+
}
|
|
158643
|
+
continue;
|
|
158644
|
+
}
|
|
158026
158645
|
providerName = argument;
|
|
158027
158646
|
provider = makeActive();
|
|
158028
158647
|
continue;
|
|
@@ -158032,8 +158651,7 @@ Kept the current session.
|
|
|
158032
158651
|
`);
|
|
158033
158652
|
continue;
|
|
158034
158653
|
}
|
|
158035
|
-
|
|
158036
|
-
applySelection(picked);
|
|
158654
|
+
await selectAndApply({});
|
|
158037
158655
|
continue;
|
|
158038
158656
|
}
|
|
158039
158657
|
if (command === "/connect") {
|
|
@@ -158041,8 +158659,7 @@ Kept the current session.
|
|
|
158041
158659
|
system(CONNECT_GUIDANCE);
|
|
158042
158660
|
continue;
|
|
158043
158661
|
}
|
|
158044
|
-
|
|
158045
|
-
applySelection(picked);
|
|
158662
|
+
await selectAndApply({ onlyConnected: true });
|
|
158046
158663
|
continue;
|
|
158047
158664
|
}
|
|
158048
158665
|
const wrongMode = describeUnavailableCommand(command, "chat");
|
|
@@ -158150,6 +158767,8 @@ function realMakeProvider(write) {
|
|
|
158150
158767
|
};
|
|
158151
158768
|
}
|
|
158152
158769
|
function oauthCredentialsFor(name) {
|
|
158770
|
+
if (name === "openai" || name === "openai-codex")
|
|
158771
|
+
return {};
|
|
158153
158772
|
const definition = providerByName(name);
|
|
158154
158773
|
const envKey = definition?.envKey;
|
|
158155
158774
|
if (envKey === undefined)
|
|
@@ -169437,6 +170056,7 @@ async function routingCommand(args, deps = {}) {
|
|
|
169437
170056
|
init_catalog();
|
|
169438
170057
|
init_grants();
|
|
169439
170058
|
init_login();
|
|
170059
|
+
init_open_url();
|
|
169440
170060
|
function printAuthHelp() {
|
|
169441
170061
|
console.log(`Usage:
|
|
169442
170062
|
keryx auth list [--json]
|
|
@@ -169444,7 +170064,7 @@ function printAuthHelp() {
|
|
|
169444
170064
|
keryx auth logout <provider>
|
|
169445
170065
|
keryx auth status <provider> [--json]
|
|
169446
170066
|
|
|
169447
|
-
Providers with a sanctioned subscription login: grok (SuperGrok), openai (ChatGPT Plus/Pro), github-copilot.
|
|
170067
|
+
Providers with a sanctioned subscription login: grok (SuperGrok), openai-codex (ChatGPT Plus/Pro), github-copilot.
|
|
169448
170068
|
Claude Pro/Max, Gemini Google-account login, and DeepSeek OAuth are not offered.
|
|
169449
170069
|
`);
|
|
169450
170070
|
}
|
|
@@ -169500,7 +170120,9 @@ function runAuthList(args) {
|
|
|
169500
170120
|
}
|
|
169501
170121
|
}
|
|
169502
170122
|
async function runAuthLogin(args) {
|
|
169503
|
-
const provider = args[0];
|
|
170123
|
+
const provider = args[0] === "openai" ? "openai-codex" : args[0];
|
|
170124
|
+
if (args[0] === "openai")
|
|
170125
|
+
console.error("The OpenAI subscription login is now openai-codex; openai is API-key only.");
|
|
169504
170126
|
if (provider === undefined || provider.length === 0) {
|
|
169505
170127
|
console.error("Usage: keryx auth login <provider>");
|
|
169506
170128
|
process.exitCode = 1;
|
|
@@ -169512,14 +170134,18 @@ async function runAuthLogin(args) {
|
|
|
169512
170134
|
process.exitCode = 1;
|
|
169513
170135
|
return;
|
|
169514
170136
|
}
|
|
170137
|
+
const controller = new AbortController;
|
|
170138
|
+
const cancel = () => controller.abort();
|
|
170139
|
+
process.on("SIGINT", cancel);
|
|
169515
170140
|
const result = await loginDeviceCode({
|
|
169516
170141
|
provider,
|
|
170142
|
+
signal: controller.signal,
|
|
169517
170143
|
fetch: (input, init) => globalThis.fetch(input, init),
|
|
169518
170144
|
onChallenge: (challenge) => {
|
|
169519
170145
|
console.log(challenge.instructions);
|
|
169520
170146
|
openVerificationUrl(challenge.verificationUriComplete ?? challenge.verificationUri);
|
|
169521
170147
|
}
|
|
169522
|
-
});
|
|
170148
|
+
}).finally(() => process.removeListener("SIGINT", cancel));
|
|
169523
170149
|
if (!result.ok) {
|
|
169524
170150
|
console.error(result.error);
|
|
169525
170151
|
process.exitCode = 1;
|
|
@@ -169529,7 +170155,9 @@ async function runAuthLogin(args) {
|
|
|
169529
170155
|
console.log(`Authorized ${provider} via ${deviceCodeMethodLabel(provider)}.`);
|
|
169530
170156
|
}
|
|
169531
170157
|
function runAuthLogout(args) {
|
|
169532
|
-
const provider = args[0];
|
|
170158
|
+
const provider = args[0] === "openai" ? "openai-codex" : args[0];
|
|
170159
|
+
if (args[0] === "openai")
|
|
170160
|
+
console.error("The OpenAI subscription login is now openai-codex; openai is API-key only.");
|
|
169533
170161
|
if (provider === undefined || provider.length === 0) {
|
|
169534
170162
|
console.error("Usage: keryx auth logout <provider>");
|
|
169535
170163
|
process.exitCode = 1;
|
|
@@ -169539,7 +170167,9 @@ function runAuthLogout(args) {
|
|
|
169539
170167
|
console.log(`Logged out ${provider}.`);
|
|
169540
170168
|
}
|
|
169541
170169
|
function runAuthStatus(args) {
|
|
169542
|
-
const provider = args[0];
|
|
170170
|
+
const provider = args[0] === "openai" ? "openai-codex" : args[0];
|
|
170171
|
+
if (args[0] === "openai")
|
|
170172
|
+
console.error("The OpenAI subscription login is now openai-codex; openai is API-key only.");
|
|
169543
170173
|
if (provider === undefined || provider.length === 0) {
|
|
169544
170174
|
console.error("Usage: keryx auth status <provider>");
|
|
169545
170175
|
process.exitCode = 1;
|