jeo-code 0.12.4 → 0.12.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/CHANGELOG.md +23 -1
- package/README.ja.md +2 -2
- package/README.ko.md +2 -2
- package/README.md +2 -2
- package/README.zh.md +2 -2
- package/package.json +1 -1
- package/src/ai/model-cache.ts +21 -10
- package/src/ai/model-catalog.ts +45 -2
- package/src/ai/model-discovery.ts +31 -6
- package/src/ai/model-manager.ts +49 -4
- package/src/commands/auth.ts +12 -3
- package/src/commands/provider.ts +2 -0
- package/src/commands/setup-helpers.ts +1 -0
- package/src/commands/setup.ts +16 -11
- package/src/util/provider-error.ts +34 -3
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,27 @@ The README mirrors the latest 5 entries — regenerate with `bun run changelog:s
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [0.12.6] - 2026-10-17
|
|
13
|
+
_Preserve OpenAI subscription authentication behind configured proxies, isolate account catalogs, and give provider-specific recovery instructions while retaining Antigravity OAuth calls._
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- **OpenAI subscription startup behind a configured proxy** — account-confirmed Codex models keep their OAuth transport instead of sending a keyless request to the OpenAI-compatible endpoint. Cold calls confirm the subscription catalog first; explicit per-call URLs, configured proxy keys, and endpoint-discovered models retain their existing routes. Subscription requests use the direct Codex backend, not the configured proxy.
|
|
17
|
+
- **Provider-specific authentication recovery** — HTTP 401/403 errors name the appropriate login or API-key configuration, omit unrelated Antigravity advice for OpenAI, preserve exact status codes, and no longer treat a structured non-authentication failure as a credential rejection merely because its body mentions 401.
|
|
18
|
+
- **Account-bound subscription catalogs** — distinguish opaque bearer identities without exposing tokens, reject discovery results that finish after an account switch, share concurrent same-account catalog requests, and keep cancellation of one caller from interrupting other callers.
|
|
19
|
+
|
|
20
|
+
## [0.12.5] - 2026-10-17
|
|
21
|
+
_Antigravity and Kimi provider support in interactive setup, Kimi live model cache rehydration, immediate discovery caching on provider probing._
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- **Interactive Setup Provider Options** (`src/commands/setup.ts`, `src/commands/setup-helpers.ts`) — added `antigravity` (Google Cloud Code Assist OAuth) and `kimi` (Moonshot API key / Kimi Code OAuth) to interactive setup provider selection, enabling browser OAuth, bearer token paste, or API key configuration with default models (`antigravity/gemini-3-flash`, `kimi-k2.5`). Updated `buildEnabledProviders` to recognize configured `kimi` credentials.
|
|
25
|
+
- **Kimi Live Model Cache Rehydration** (`src/ai/model-cache.ts`) — cache hits for Kimi OAuth (`provider === 'kimi' && source === 'oauth'`) now rehydrate `recordLiveKimiCodeModels(entry.models)` alongside OpenAI Codex models.
|
|
26
|
+
- **Immediate Model Cache on Provider Probe** (`src/commands/provider.ts`) — successful `jeo provider test <id>` and `jeo provider add` probing now immediately writes discovered custom/compatible models to the disk cache (`writeModelCache([result])`).
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
- **Model Cache Key Scoping** (`src/ai/model-cache.ts`) — fixed `keyOf` helper scoping and included `kimi` in source distinction so OAuth and API key catalogs do not clobber each other.
|
|
30
|
+
- **OAuth Token Preservation in Setup** (`src/commands/setup.ts`) — fixed `delete next.oauth` condition to check `Object.keys(next.oauth).length === 0` so non-Anthropic/OpenAI/Gemini OAuth credentials (such as `antigravity` or `kimi`) are not inadvertently dropped on save.
|
|
31
|
+
- **Auth Import Model Reporting** (`src/commands/auth.ts`) — added `antigravity` reporting to `reportLiveModels` following Gemini OAuth import.
|
|
32
|
+
|
|
12
33
|
## [0.12.4] - 2026-10-17
|
|
13
34
|
_Refresh Antigravity provider (`antigravity/*`) account catalogs with `antigravity/hub/2.23.1` so entitled Gemini 4 Argon and Gemini 3.8 Flash tiers appear directly in `/model provider antigravity`._
|
|
14
35
|
|
|
@@ -2566,7 +2587,8 @@ _Initial release._
|
|
|
2566
2587
|
### Added
|
|
2567
2588
|
- Initial jeo-code agent and CLI.
|
|
2568
2589
|
|
|
2569
|
-
[Unreleased]: https://github.com/akillness/jeo-code/compare/v0.
|
|
2590
|
+
[Unreleased]: https://github.com/akillness/jeo-code/compare/v0.12.6...HEAD
|
|
2591
|
+
[0.12.6]: https://github.com/akillness/jeo-code/releases/tag/v0.12.6
|
|
2570
2592
|
[0.11.4]: https://github.com/akillness/jeo-code/releases/tag/v0.11.4
|
|
2571
2593
|
[0.11.3]: https://github.com/akillness/jeo-code/releases/tag/v0.11.3
|
|
2572
2594
|
[0.11.2]: https://github.com/akillness/jeo-code/releases/tag/v0.11.2
|
package/README.ja.md
CHANGED
|
@@ -416,10 +416,10 @@ CI は `.github/workflows/npm-publish.yml` で公開します — GitHub リリ
|
|
|
416
416
|
|
|
417
417
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
418
418
|
- **[Unreleased]**
|
|
419
|
+
- **[0.12.6]** (2026-10-17) — Preserve OpenAI subscription authentication behind configured proxies, isolate account catalogs, and give provider-specific recovery instructions while retaining Antigravity OAuth calls.
|
|
420
|
+
- **[0.12.5]** (2026-10-17) — Antigravity and Kimi provider support in interactive setup, Kimi live model cache rehydration, immediate discovery caching on provider probing.
|
|
419
421
|
- **[0.12.4]** (2026-10-17) — Refresh Antigravity provider (`antigravity/*`) account catalogs with `antigravity/hub/2.23.1` so entitled Gemini 4 Argon and Gemini 3.8 Flash tiers appear directly in `/model provider antigravity`.
|
|
420
422
|
- **[0.12.3]** (2026-10-17) — Anthropic static model catalog trimmed to claude-5.x with live API discovery, and `/route off` strictly fixes the active model with no failover.
|
|
421
|
-
- **[0.12.2]** (2026-10-16) — Select Argon through the official agy full agent, with its own live catalog and session, separately from jeo's Cloud Code Assist backend.
|
|
422
|
-
- **[0.12.1]** (2026-10-16) — Logged-in OpenAI subscription models were invisible on proxy-configured machines.
|
|
423
423
|
|
|
424
424
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
425
425
|
<!-- CHANGELOG:END -->
|
package/README.ko.md
CHANGED
|
@@ -417,10 +417,10 @@ CI는 `.github/workflows/npm-publish.yml`로 배포합니다 — GitHub 릴리
|
|
|
417
417
|
|
|
418
418
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
419
419
|
- **[Unreleased]**
|
|
420
|
+
- **[0.12.6]** (2026-10-17) — Preserve OpenAI subscription authentication behind configured proxies, isolate account catalogs, and give provider-specific recovery instructions while retaining Antigravity OAuth calls.
|
|
421
|
+
- **[0.12.5]** (2026-10-17) — Antigravity and Kimi provider support in interactive setup, Kimi live model cache rehydration, immediate discovery caching on provider probing.
|
|
420
422
|
- **[0.12.4]** (2026-10-17) — Refresh Antigravity provider (`antigravity/*`) account catalogs with `antigravity/hub/2.23.1` so entitled Gemini 4 Argon and Gemini 3.8 Flash tiers appear directly in `/model provider antigravity`.
|
|
421
423
|
- **[0.12.3]** (2026-10-17) — Anthropic static model catalog trimmed to claude-5.x with live API discovery, and `/route off` strictly fixes the active model with no failover.
|
|
422
|
-
- **[0.12.2]** (2026-10-16) — Select Argon through the official agy full agent, with its own live catalog and session, separately from jeo's Cloud Code Assist backend.
|
|
423
|
-
- **[0.12.1]** (2026-10-16) — Logged-in OpenAI subscription models were invisible on proxy-configured machines.
|
|
424
424
|
|
|
425
425
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
426
426
|
<!-- CHANGELOG:END -->
|
package/README.md
CHANGED
|
@@ -409,10 +409,10 @@ Huge thanks to [gajae-code](https://github.com/Yeachan-Heo/gajae-code) for the i
|
|
|
409
409
|
|
|
410
410
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
411
411
|
- **[Unreleased]**
|
|
412
|
+
- **[0.12.6]** (2026-10-17) — Preserve OpenAI subscription authentication behind configured proxies, isolate account catalogs, and give provider-specific recovery instructions while retaining Antigravity OAuth calls.
|
|
413
|
+
- **[0.12.5]** (2026-10-17) — Antigravity and Kimi provider support in interactive setup, Kimi live model cache rehydration, immediate discovery caching on provider probing.
|
|
412
414
|
- **[0.12.4]** (2026-10-17) — Refresh Antigravity provider (`antigravity/*`) account catalogs with `antigravity/hub/2.23.1` so entitled Gemini 4 Argon and Gemini 3.8 Flash tiers appear directly in `/model provider antigravity`.
|
|
413
415
|
- **[0.12.3]** (2026-10-17) — Anthropic static model catalog trimmed to claude-5.x with live API discovery, and `/route off` strictly fixes the active model with no failover.
|
|
414
|
-
- **[0.12.2]** (2026-10-16) — Select Argon through the official agy full agent, with its own live catalog and session, separately from jeo's Cloud Code Assist backend.
|
|
415
|
-
- **[0.12.1]** (2026-10-16) — Logged-in OpenAI subscription models were invisible on proxy-configured machines.
|
|
416
416
|
|
|
417
417
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
418
418
|
<!-- CHANGELOG:END -->
|
package/README.zh.md
CHANGED
|
@@ -414,10 +414,10 @@ CI 通过 `.github/workflows/npm-publish.yml` 发布 — GitHub 发布 release
|
|
|
414
414
|
|
|
415
415
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
416
416
|
- **[Unreleased]**
|
|
417
|
+
- **[0.12.6]** (2026-10-17) — Preserve OpenAI subscription authentication behind configured proxies, isolate account catalogs, and give provider-specific recovery instructions while retaining Antigravity OAuth calls.
|
|
418
|
+
- **[0.12.5]** (2026-10-17) — Antigravity and Kimi provider support in interactive setup, Kimi live model cache rehydration, immediate discovery caching on provider probing.
|
|
417
419
|
- **[0.12.4]** (2026-10-17) — Refresh Antigravity provider (`antigravity/*`) account catalogs with `antigravity/hub/2.23.1` so entitled Gemini 4 Argon and Gemini 3.8 Flash tiers appear directly in `/model provider antigravity`.
|
|
418
420
|
- **[0.12.3]** (2026-10-17) — Anthropic static model catalog trimmed to claude-5.x with live API discovery, and `/route off` strictly fixes the active model with no failover.
|
|
419
|
-
- **[0.12.2]** (2026-10-16) — Select Argon through the official agy full agent, with its own live catalog and session, separately from jeo's Cloud Code Assist backend.
|
|
420
|
-
- **[0.12.1]** (2026-10-16) — Logged-in OpenAI subscription models were invisible on proxy-configured machines.
|
|
421
421
|
|
|
422
422
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
423
423
|
<!-- CHANGELOG:END -->
|
package/package.json
CHANGED
package/src/ai/model-cache.ts
CHANGED
|
@@ -19,7 +19,7 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
|
19
19
|
import * as os from "node:os";
|
|
20
20
|
import * as path from "node:path";
|
|
21
21
|
import { jeoEnv } from "../util/env";
|
|
22
|
-
import { recordLiveCodexModels, recordLiveProviderModels, setAntigravityAccountScope, setOpenAIOauthAccountScope } from "./model-catalog";
|
|
22
|
+
import { recordLiveCodexModels, recordLiveKimiCodeModels, recordLiveProviderModels, setAntigravityAccountScope, setOpenAIOauthAccountScope } from "./model-catalog";
|
|
23
23
|
import type { Credential } from "../auth";
|
|
24
24
|
import { getStoredOAuth, snapshotProvider } from "../auth/storage";
|
|
25
25
|
import { getAntigravityUserAgent } from "./providers/antigravity";
|
|
@@ -60,6 +60,21 @@ function cachePath(): string {
|
|
|
60
60
|
return path.join(cacheDir(), "model-catalog-cache.json");
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/** Account-bound catalog identity; opaque manual bearers must not share a scope. */
|
|
64
|
+
export async function openAIOauthAccountScope(credential?: Credential): Promise<string | undefined> {
|
|
65
|
+
let selected = credential;
|
|
66
|
+
if (!selected) {
|
|
67
|
+
const snapshot = await snapshotProvider("openai");
|
|
68
|
+
if (!snapshot.oauth) return undefined;
|
|
69
|
+
selected = { kind: "oauth", provider: "openai", token: snapshot.oauth };
|
|
70
|
+
}
|
|
71
|
+
if (selected.kind !== "oauth" || selected.provider !== "openai") return undefined;
|
|
72
|
+
const stored = await getStoredOAuth("openai");
|
|
73
|
+
const accountId = extractChatgptAccountId(selected.token)
|
|
74
|
+
|| (stored?.access === selected.token ? stored.accountId?.trim() : undefined);
|
|
75
|
+
return accountId || `token:${createHash("sha256").update(selected.token).digest("hex")}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
63
78
|
/** Peek at the selected OAuth account without importing or refreshing credentials. */
|
|
64
79
|
export async function antigravityAccountScope(credential?: Credential): Promise<string | undefined> {
|
|
65
80
|
try {
|
|
@@ -158,7 +173,7 @@ export function mergeCacheEntries(
|
|
|
158
173
|
const isScopedOAuth = (provider: ProviderName, source: ModelCacheSource): boolean =>
|
|
159
174
|
(provider === "openai" || provider === "antigravity") && source === "oauth";
|
|
160
175
|
const keyOf = (provider: ProviderName, baseUrl?: string, accountId?: string, source?: ModelCacheSource) =>
|
|
161
|
-
`${provider}\u0000${baseUrl?.replace(/\/+$/, "") ?? ""}\u0000${isScopedOAuth(provider, source ?? "none") ? accountId ?? "" : ""}\u0000${provider === "openai" || provider === "antigravity" ? source ?? "none" : ""}`;
|
|
176
|
+
`${provider}\u0000${baseUrl?.replace(/\/+$/, "") ?? ""}\u0000${isScopedOAuth(provider, source ?? "none") ? accountId ?? "" : ""}\u0000${provider === "openai" || provider === "antigravity" || provider === "kimi" ? source ?? "none" : ""}`;
|
|
162
177
|
for (const entry of previous) {
|
|
163
178
|
const scoped = isScopedOAuth(entry.provider, entry.source);
|
|
164
179
|
if (entry.provider === "agy") continue;
|
|
@@ -240,6 +255,9 @@ export function applyCachedModels(cache: ModelCacheFile | null, openAIOauthAccou
|
|
|
240
255
|
if (isOpenAIOAuth && entry.accountId && !entry.baseUrl) {
|
|
241
256
|
recordLiveCodexModels(entry.models, entry.accountId);
|
|
242
257
|
}
|
|
258
|
+
if (entry.provider === "kimi" && entry.source === "oauth") {
|
|
259
|
+
recordLiveKimiCodeModels(entry.models);
|
|
260
|
+
}
|
|
243
261
|
applied += entry.models.length;
|
|
244
262
|
}
|
|
245
263
|
return applied;
|
|
@@ -248,14 +266,7 @@ export function applyCachedModels(cache: ModelCacheFile | null, openAIOauthAccou
|
|
|
248
266
|
/** Read + apply in one step. Returns the cache so callers can decide about refreshing. */
|
|
249
267
|
export async function rehydrateLiveModels(): Promise<ModelCacheFile | null> {
|
|
250
268
|
const cache = await readModelCache();
|
|
251
|
-
|
|
252
|
-
try {
|
|
253
|
-
const stored = await getStoredOAuth("openai");
|
|
254
|
-
const jwtAccountId = stored ? extractChatgptAccountId(stored.access) : undefined;
|
|
255
|
-
accountId = jwtAccountId || stored?.accountId;
|
|
256
|
-
} catch {
|
|
257
|
-
accountId = undefined;
|
|
258
|
-
}
|
|
269
|
+
const accountId = await openAIOauthAccountScope();
|
|
259
270
|
const antigravityAccountId = await antigravityAccountScope();
|
|
260
271
|
setOpenAIOauthAccountScope(accountId);
|
|
261
272
|
setAntigravityAccountScope(antigravityAccountId);
|
package/src/ai/model-catalog.ts
CHANGED
|
@@ -255,12 +255,17 @@ export function recordLiveCodexModels(ids: readonly string[], accountId?: string
|
|
|
255
255
|
}
|
|
256
256
|
|
|
257
257
|
/** A confirmed account catalog is authoritative; use the snapshot before discovery. */
|
|
258
|
-
export function isCodexModel(model: string): boolean {
|
|
258
|
+
export function isCodexModel(model: string, accountId = activeOpenAIOauthScope): boolean {
|
|
259
259
|
const wire = model.startsWith("openai/") ? model.slice(7) : model;
|
|
260
|
-
const confirmed = liveCodexModels.get(
|
|
260
|
+
const confirmed = liveCodexModels.get(oauthScope(accountId));
|
|
261
261
|
return confirmed ? confirmed.has(wire) : CODEX_MODELS.includes(wire);
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
+
/** Whether discovery (including a restored account cache) established a Codex catalog. */
|
|
265
|
+
export function hasConfirmedCodexCatalog(accountId = activeOpenAIOauthScope): boolean {
|
|
266
|
+
return liveCodexModels.has(oauthScope(accountId));
|
|
267
|
+
}
|
|
268
|
+
|
|
264
269
|
/** Test-only reset for live Codex observations and the active account scope. */
|
|
265
270
|
export function resetLiveCodexModels(): void {
|
|
266
271
|
liveCodexModels.clear();
|
|
@@ -501,6 +506,30 @@ export const KIMI_CODE_MODELS: readonly string[] = [
|
|
|
501
506
|
"kimi-k2",
|
|
502
507
|
];
|
|
503
508
|
|
|
509
|
+
const liveKimiCodeModels = new Set<string>();
|
|
510
|
+
|
|
511
|
+
/** Record ids confirmed by the Kimi Code OAuth `/v1/models` endpoint so brand-new
|
|
512
|
+
* subscription models work immediately without waiting for a static catalog release. */
|
|
513
|
+
export function recordLiveKimiCodeModels(ids: readonly string[]): void {
|
|
514
|
+
for (const id of ids) {
|
|
515
|
+
const trimmed = id.trim();
|
|
516
|
+
if (!trimmed) continue;
|
|
517
|
+
const wire = trimmed.startsWith("kimi/") ? trimmed.slice(5) : trimmed;
|
|
518
|
+
if (wire) liveKimiCodeModels.add(wire);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/** Whether `model` is served by the Kimi Code OAuth subscription endpoint. */
|
|
523
|
+
export function isKimiCodeModel(model: string): boolean {
|
|
524
|
+
const wire = model.startsWith("kimi/") ? model.slice(5) : model;
|
|
525
|
+
return KIMI_CODE_MODELS.includes(wire) || liveKimiCodeModels.has(wire);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/** Test-only reset for live Kimi Code observations. */
|
|
529
|
+
export function resetLiveKimiCodeModels(): void {
|
|
530
|
+
liveKimiCodeModels.clear();
|
|
531
|
+
}
|
|
532
|
+
|
|
504
533
|
/** Recency compare for `releaseDate` ("YYYY-MM"): `>0` when `a` is newer; a
|
|
505
534
|
* missing date sorts oldest, so an unconfirmed date never outranks a
|
|
506
535
|
* verified-newer model. */
|
|
@@ -639,6 +668,20 @@ export function inferCatalogMetadata(modelId: string): CatalogModel | undefined
|
|
|
639
668
|
company: "xAI",
|
|
640
669
|
};
|
|
641
670
|
}
|
|
671
|
+
// Kimi / Moonshot
|
|
672
|
+
const kimi = id.match(/^kimi-(k\d+|latest|for-coding)/);
|
|
673
|
+
if (kimi) {
|
|
674
|
+
return {
|
|
675
|
+
canonical: raw,
|
|
676
|
+
provider: "kimi",
|
|
677
|
+
providerModel: id,
|
|
678
|
+
contextTokens: 256_000,
|
|
679
|
+
maxOutputTokens: 16_384,
|
|
680
|
+
thinking: FULL,
|
|
681
|
+
images: false,
|
|
682
|
+
company: "Moonshot",
|
|
683
|
+
};
|
|
684
|
+
}
|
|
642
685
|
|
|
643
686
|
return undefined;
|
|
644
687
|
}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
catalogByProvider,
|
|
17
17
|
KIMI_CODE_MODELS,
|
|
18
18
|
recordLiveCodexModels,
|
|
19
|
+
recordLiveKimiCodeModels,
|
|
19
20
|
recordLiveProviderModels,
|
|
20
21
|
setOpenAIOauthAccountScope,
|
|
21
22
|
setAntigravityAccountScope,
|
|
@@ -23,10 +24,10 @@ import {
|
|
|
23
24
|
import { extractChatgptAccountId } from "./providers/openai-responses";
|
|
24
25
|
import { getStoredOAuth } from "../auth/storage";
|
|
25
26
|
import { openaiCompatDef } from "./providers/openai-compatible-catalog";
|
|
26
|
-
import { antigravityAccountScope } from "./model-cache";
|
|
27
|
+
import { antigravityAccountScope, openAIOauthAccountScope } from "./model-cache";
|
|
27
28
|
import { getAntigravityUserAgent, resolveAntigravityProjectId } from "./providers/antigravity";
|
|
28
29
|
import { discoverGoogleProjectId, ANTIGRAVITY_DISCOVERY_METADATA } from "../auth/flows/google-project";
|
|
29
|
-
|
|
30
|
+
import { getKimiCommonHeaders } from "../auth/flows/kimi";
|
|
30
31
|
export interface ProviderModelsResult {
|
|
31
32
|
provider: ProviderName;
|
|
32
33
|
/** Discovered model ids (provider-qualified where the router expects it). */
|
|
@@ -185,6 +186,15 @@ export function discoveryRequest(
|
|
|
185
186
|
return { url: "https://api.x.ai/v1/models", headers: token ? { Authorization: `Bearer ${token}` } : {} };
|
|
186
187
|
}
|
|
187
188
|
case "kimi": {
|
|
189
|
+
if (cred?.kind === "oauth") {
|
|
190
|
+
return {
|
|
191
|
+
url: "https://api.kimi.com/coding/v1/models",
|
|
192
|
+
headers: {
|
|
193
|
+
...getKimiCommonHeaders(),
|
|
194
|
+
Authorization: `Bearer ${cred.token}`,
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}
|
|
188
198
|
const token = cred?.kind === "api_key" ? cred.token : "";
|
|
189
199
|
return { url: "https://api.moonshot.ai/v1/models", headers: token ? { Authorization: `Bearer ${token}` } : {} };
|
|
190
200
|
}
|
|
@@ -402,6 +412,7 @@ export async function listProviderModels(
|
|
|
402
412
|
|
|
403
413
|
let cred: Credential | undefined;
|
|
404
414
|
let oauthAccountId: string | undefined;
|
|
415
|
+
let oauthCatalogScope: string | undefined;
|
|
405
416
|
let source: ProviderModelsResult["source"] = "keyless";
|
|
406
417
|
if (provider === "xai") {
|
|
407
418
|
// xAI (Grok) is API-key only and not an OAuth AuthProvider: resolve its key
|
|
@@ -456,8 +467,9 @@ export async function listProviderModels(
|
|
|
456
467
|
if (provider === "openai" && source === "oauth" && cred.kind === "oauth") {
|
|
457
468
|
const stored = await getStoredOAuth("openai").catch(() => undefined);
|
|
458
469
|
const jwtAccountId = extractChatgptAccountId(cred.token);
|
|
459
|
-
oauthAccountId = jwtAccountId || stored?.accountId;
|
|
460
|
-
|
|
470
|
+
oauthAccountId = jwtAccountId || (stored?.access === cred.token ? stored.accountId : undefined);
|
|
471
|
+
oauthCatalogScope = await openAIOauthAccountScope(cred);
|
|
472
|
+
setOpenAIOauthAccountScope(oauthCatalogScope);
|
|
461
473
|
}
|
|
462
474
|
// Gemini models are exposed ONLY under API-key auth: a gemini OAuth token can
|
|
463
475
|
// still list models from generativelanguage.googleapis.com, but it can no
|
|
@@ -474,7 +486,7 @@ export async function listProviderModels(
|
|
|
474
486
|
}
|
|
475
487
|
}
|
|
476
488
|
|
|
477
|
-
const scope = { ...(
|
|
489
|
+
const scope = { ...(oauthCatalogScope ? { accountId: oauthCatalogScope } : {}), ...(baseUrl ? { baseUrl } : {}) };
|
|
478
490
|
const timeout = opts.timeoutMs ?? DEFAULT_TIMEOUT;
|
|
479
491
|
let signal = opts.signal ? AbortSignal.any([opts.signal, AbortSignal.timeout(timeout)]) : AbortSignal.timeout(timeout);
|
|
480
492
|
let initialAntigravityScope: string | undefined;
|
|
@@ -568,6 +580,12 @@ export async function listProviderModels(
|
|
|
568
580
|
}
|
|
569
581
|
const allModels = [...new Set(ids)].sort();
|
|
570
582
|
const models = allModels.slice(0, limit);
|
|
583
|
+
if (provider === "openai" && source === "oauth" && !baseUrl) {
|
|
584
|
+
if (await openAIOauthAccountScope() !== oauthCatalogScope) {
|
|
585
|
+
return { provider, models: [], ok: false, source, error: "account changed during model discovery", ...scope };
|
|
586
|
+
}
|
|
587
|
+
setOpenAIOauthAccountScope(oauthCatalogScope);
|
|
588
|
+
}
|
|
571
589
|
if (provider === "antigravity") {
|
|
572
590
|
const currentScope = await antigravityAccountScope();
|
|
573
591
|
if (currentScope !== scope.accountId && currentScope !== initialAntigravityScope) {
|
|
@@ -584,7 +602,8 @@ export async function listProviderModels(
|
|
|
584
602
|
replace: provider === "openai" || provider === "antigravity",
|
|
585
603
|
...(source === "oauth" && scope.accountId ? { accountId: scope.accountId } : {}),
|
|
586
604
|
});
|
|
587
|
-
if (provider === "openai" && source === "oauth" && !baseUrl) recordLiveCodexModels(allModels,
|
|
605
|
+
if (provider === "openai" && source === "oauth" && !baseUrl) recordLiveCodexModels(allModels, oauthCatalogScope);
|
|
606
|
+
if (provider === "kimi" && source === "oauth") recordLiveKimiCodeModels(allModels);
|
|
588
607
|
return {
|
|
589
608
|
provider,
|
|
590
609
|
models,
|
|
@@ -659,6 +678,12 @@ export async function discoverModels(
|
|
|
659
678
|
return [primary, await listProviderModels(p, { ...discoveryOpts, baseUrl: undefined, preferOAuth: true, codexCatalog: true })];
|
|
660
679
|
}
|
|
661
680
|
}
|
|
681
|
+
if (p === "kimi" && primary.source !== "oauth") {
|
|
682
|
+
const credential = await resolveCredential("kimi");
|
|
683
|
+
if (credential.kind === "oauth") {
|
|
684
|
+
return [primary, await listProviderModels(p, { ...discoveryOpts, preferOAuth: true })];
|
|
685
|
+
}
|
|
686
|
+
}
|
|
662
687
|
return [primary];
|
|
663
688
|
}),
|
|
664
689
|
);
|
package/src/ai/model-manager.ts
CHANGED
|
@@ -6,12 +6,15 @@ import "./register-providers"; // side-effect: registers built-in adapters into
|
|
|
6
6
|
import type { CallOptions, Message, ProviderAdapter, ProviderName } from "./types";
|
|
7
7
|
import { expandAlias, resolveModelId, effectiveAliasesFor, BUILTIN_ALIASES } from "./model-registry";
|
|
8
8
|
import { findCatalogEntry, type ModelCatalogEntry } from "./model-catalog-compat";
|
|
9
|
-
import { toProviderModel, CODEX_MODELS, KIMI_CODE_MODELS, findCatalogModel, isCodexModel, findLiveCatalogModel } from "./model-catalog";
|
|
9
|
+
import { toProviderModel, CODEX_MODELS, KIMI_CODE_MODELS, findCatalogModel, isCodexModel, isKimiCodeModel, findLiveCatalogModel, hasConfirmedCodexCatalog, isLiveProviderModel } from "./model-catalog";
|
|
10
10
|
import { xaiCredential } from "./providers/xai";
|
|
11
11
|
import { allCompatNames, isOpenAICompatProvider } from "./providers/openai-compatible-catalog";
|
|
12
12
|
import { withRetry, defaultRetryable, withConnectionContext, isConnectionError, ConnectionContextError, type RetryOptions } from "../util/retry";
|
|
13
13
|
import { jeoEnv } from "../util/env";
|
|
14
14
|
import type { Config } from "../agent/state";
|
|
15
|
+
import { listProviderModels } from "./model-discovery";
|
|
16
|
+
import type { ProviderModelsResult } from "./model-discovery";
|
|
17
|
+
import { openAIOauthAccountScope } from "./model-cache";
|
|
15
18
|
|
|
16
19
|
|
|
17
20
|
|
|
@@ -329,8 +332,7 @@ export function oauthServesModel(provider: AuthProvider, model: string): boolean
|
|
|
329
332
|
return isCodexModel(model);
|
|
330
333
|
}
|
|
331
334
|
if (provider === "kimi") {
|
|
332
|
-
|
|
333
|
-
return KIMI_CODE_MODELS.includes(wire);
|
|
335
|
+
return isKimiCodeModel(model);
|
|
334
336
|
}
|
|
335
337
|
if (isOAuthProvider(provider) && OAUTH_FLOW_REGISTRY[provider].verifiedEndToEnd === false) return false;
|
|
336
338
|
return true;
|
|
@@ -405,6 +407,8 @@ interface Resolved {
|
|
|
405
407
|
baseUrl: string | undefined;
|
|
406
408
|
}
|
|
407
409
|
|
|
410
|
+
const codexCatalogInFlight = new Map<string, Promise<ProviderModelsResult>>();
|
|
411
|
+
|
|
408
412
|
/**
|
|
409
413
|
* The credential to actually use for a provider call. A configured local OpenAI-compatible base
|
|
410
414
|
* URL must use the standard /chat/completions path, but the openai adapter dispatches on
|
|
@@ -437,7 +441,7 @@ export async function resolveCall(options: Partial<CallOptions>, kind: "request"
|
|
|
437
441
|
}
|
|
438
442
|
const adapter = providerRegistry.get(provider)!;
|
|
439
443
|
|
|
440
|
-
|
|
444
|
+
let baseUrl =
|
|
441
445
|
options.baseUrl ??
|
|
442
446
|
(provider === "openai" ? config.openaiBaseUrl : undefined) ??
|
|
443
447
|
(provider === "ollama" ? config.ollamaBaseUrl : undefined) ??
|
|
@@ -504,6 +508,47 @@ export async function resolveCall(options: Partial<CallOptions>, kind: "request"
|
|
|
504
508
|
const credentialProvider = provider as AuthProvider;
|
|
505
509
|
const credential = await resolveCredential(credentialProvider);
|
|
506
510
|
const effective = effectiveCredentialForProvider(credentialProvider, credential, config, model);
|
|
511
|
+
// A subscription catalog is independent of a configured OpenAI-compatible
|
|
512
|
+
// endpoint. Keep its OAuth transport rather than replacing it with "no-key".
|
|
513
|
+
// Explicit endpoint overrides, proxy keys and endpoint-discovered models win.
|
|
514
|
+
if (provider === "openai" && baseUrl && options.baseUrl === undefined
|
|
515
|
+
&& effective.kind === "oauth" && !config.providers.openai
|
|
516
|
+
&& !isLiveProviderModel("openai", model, { openaiBaseUrl: baseUrl })) {
|
|
517
|
+
options.signal?.throwIfAborted();
|
|
518
|
+
const accountScope = await openAIOauthAccountScope(effective);
|
|
519
|
+
if (accountScope && !hasConfirmedCodexCatalog(accountScope)) {
|
|
520
|
+
let discovery = codexCatalogInFlight.get(accountScope);
|
|
521
|
+
if (!discovery) {
|
|
522
|
+
// One caller's cancellation must not cancel another agent's catalog read.
|
|
523
|
+
discovery = listProviderModels("openai", { config, codexCatalog: true });
|
|
524
|
+
codexCatalogInFlight.set(accountScope, discovery);
|
|
525
|
+
void discovery.finally(() => codexCatalogInFlight.delete(accountScope)).catch(() => {});
|
|
526
|
+
}
|
|
527
|
+
const signal = options.signal;
|
|
528
|
+
if (signal) {
|
|
529
|
+
const aborted = Promise.withResolvers<never>();
|
|
530
|
+
const onAbort = () => aborted.reject(signal.reason);
|
|
531
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
532
|
+
try {
|
|
533
|
+
signal.throwIfAborted();
|
|
534
|
+
await Promise.race([discovery, aborted.promise]);
|
|
535
|
+
} finally {
|
|
536
|
+
signal.removeEventListener("abort", onAbort);
|
|
537
|
+
}
|
|
538
|
+
} else {
|
|
539
|
+
await discovery;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
options.signal?.throwIfAborted();
|
|
543
|
+
if (await openAIOauthAccountScope() !== accountScope) {
|
|
544
|
+
throw new Error("OpenAI credential changed during model discovery; retry the request.");
|
|
545
|
+
}
|
|
546
|
+
// Never infer cloud eligibility from the bundled snapshot when discovery fails.
|
|
547
|
+
if (accountScope && hasConfirmedCodexCatalog(accountScope) && isCodexModel(model, accountScope)) {
|
|
548
|
+
baseUrl = undefined;
|
|
549
|
+
callOptions.baseUrl = undefined;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
507
552
|
const isLocalOpenAi = provider === "openai" && !!baseUrl;
|
|
508
553
|
if (provider === "openai" && effective.kind === "oauth" && !isLocalOpenAi && !oauthServesModel("openai", model)) {
|
|
509
554
|
throw new Error(
|
package/src/commands/auth.ts
CHANGED
|
@@ -152,7 +152,7 @@ async function runAuthLogin(rest: string[]): Promise<void> {
|
|
|
152
152
|
* already succeeded and is not rolled back by this check. Prints only model
|
|
153
153
|
* ids/count — no tokens/secrets.
|
|
154
154
|
*/
|
|
155
|
-
const LIVE_MODEL_REPORT_PROVIDERS = new Set<AuthProvider>(["openai", "anthropic", "antigravity"]);
|
|
155
|
+
const LIVE_MODEL_REPORT_PROVIDERS = new Set<AuthProvider>(["openai", "anthropic", "antigravity", "kimi"]);
|
|
156
156
|
|
|
157
157
|
async function reportLiveModels(provider: AuthProvider): Promise<void> {
|
|
158
158
|
try {
|
|
@@ -249,6 +249,7 @@ async function runAuthImport(rest: string[]): Promise<void> {
|
|
|
249
249
|
}
|
|
250
250
|
const expiryMsg = expires ? fmtExpiry(expires) : "";
|
|
251
251
|
console.log(`Expiry status:${expiryMsg || " valid"}`);
|
|
252
|
+
await reportLiveModels("antigravity");
|
|
252
253
|
} catch (err: any) {
|
|
253
254
|
console.log(`[FAILED] ${err.message}`);
|
|
254
255
|
process.exitCode = 1;
|
|
@@ -335,8 +336,16 @@ async function selectProvider(
|
|
|
335
336
|
console.log(" 1) anthropic (real PKCE OAuth, verified end-to-end)");
|
|
336
337
|
console.log(" 2) openai (real PKCE OAuth, Codex backend)");
|
|
337
338
|
console.log(" 3) gemini (real Google OAuth, Cloud Code Assist)");
|
|
338
|
-
|
|
339
|
-
|
|
339
|
+
console.log(" 4) antigravity (Google Cloud Code Assist OAuth)");
|
|
340
|
+
console.log(" 5) kimi (Kimi Code OAuth device authorization)");
|
|
341
|
+
const ans = (await rl.question("Choose [1-5]: ")).trim();
|
|
342
|
+
const map: Record<string, AuthProvider> = {
|
|
343
|
+
"1": "anthropic",
|
|
344
|
+
"2": "openai",
|
|
345
|
+
"3": "gemini",
|
|
346
|
+
"4": "antigravity",
|
|
347
|
+
"5": "kimi",
|
|
348
|
+
};
|
|
340
349
|
const p = map[ans];
|
|
341
350
|
if (!p) {
|
|
342
351
|
console.log("Invalid choice.");
|
package/src/commands/provider.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
normalizeProviderId,
|
|
32
32
|
} from "../ai/providers/custom-providers";
|
|
33
33
|
import { listProviderModels } from "../ai/model-discovery";
|
|
34
|
+
import { writeModelCache } from "../ai/model-cache";
|
|
34
35
|
import { describeProvider } from "../ai/provider-status";
|
|
35
36
|
|
|
36
37
|
const USAGE = [
|
|
@@ -186,6 +187,7 @@ async function probe(id: string, opts: { failOnError: boolean }): Promise<void>
|
|
|
186
187
|
if (shown.length) console.log(` ${shown.join(", ")}${result.models.length > shown.length ? ", …" : ""}`);
|
|
187
188
|
const first = result.models[0];
|
|
188
189
|
console.log(` status : OK — use it with 'jeo launch --model ${first ? qualify(first) : `${id}/<model>`}'`);
|
|
190
|
+
await writeModelCache([result]);
|
|
189
191
|
return;
|
|
190
192
|
}
|
|
191
193
|
|
|
@@ -76,6 +76,7 @@ export function buildEnabledProviders(config: Config): string[] {
|
|
|
76
76
|
if (cfg.providers?.anthropic || cfg.oauth?.anthropic) enabled.push("anthropic");
|
|
77
77
|
if (cfg.providers?.openai || cfg.oauth?.openai) enabled.push("openai");
|
|
78
78
|
if (cfg.providers?.gemini || cfg.oauth?.gemini || cfg.providers?.antigravity || cfg.oauth?.antigravity) enabled.push("google-oauth");
|
|
79
|
+
if (cfg.providers?.kimi || cfg.oauth?.kimi) enabled.push("kimi");
|
|
79
80
|
if (cfg.ollamaBaseUrl) enabled.push(`ollama(${cfg.ollamaBaseUrl})`);
|
|
80
81
|
if (cfg.openaiBaseUrl) enabled.push(`openai-compatible(${cfg.openaiBaseUrl})`);
|
|
81
82
|
return enabled;
|
package/src/commands/setup.ts
CHANGED
|
@@ -24,12 +24,14 @@ function reportModelChoice(r: { warning?: string; suggestions: string[] }): void
|
|
|
24
24
|
if (r.suggestions.length) console.log(` Did you mean: ${r.suggestions.join(", ")}?`);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
type ProviderChoice = "anthropic" | "openai" | "gemini" | "ollama" | "lmstudio" | "openai-compatible";
|
|
27
|
+
type ProviderChoice = "anthropic" | "openai" | "gemini" | "antigravity" | "kimi" | "ollama" | "lmstudio" | "openai-compatible";
|
|
28
28
|
|
|
29
29
|
const DEFAULT_MODELS: Record<ProviderChoice, string> = {
|
|
30
30
|
anthropic: "claude-sonnet-4-6",
|
|
31
31
|
openai: "gpt-4o",
|
|
32
32
|
gemini: "gemini-2.0-flash",
|
|
33
|
+
antigravity: "antigravity/gemini-3-flash",
|
|
34
|
+
kimi: "kimi-k2.5",
|
|
33
35
|
ollama: "ollama/llama3.1:8b",
|
|
34
36
|
lmstudio: "openai/local-model",
|
|
35
37
|
"openai-compatible": "openai/local-model",
|
|
@@ -87,16 +89,19 @@ export async function runSetupCommand(): Promise<void> {
|
|
|
87
89
|
console.log(" 1) anthropic — Claude (API key or OAuth bearer)");
|
|
88
90
|
console.log(" 2) openai — GPT (API key or OAuth bearer)");
|
|
89
91
|
console.log(" 3) gemini — Google AI Studio (API key or OAuth bearer)");
|
|
90
|
-
console.log(" 4)
|
|
91
|
-
console.log(" 5)
|
|
92
|
-
console.log(" 6)
|
|
93
|
-
console.log(" 7)
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
console.log(" 4) antigravity — Google Cloud Code Assist (OAuth)");
|
|
93
|
+
console.log(" 5) kimi — Kimi / Moonshot (API key or Kimi Code OAuth)");
|
|
94
|
+
console.log(" 6) ollama — local Ollama (no auth)");
|
|
95
|
+
console.log(" 7) lmstudio — local LM Studio (OpenAI-compatible)");
|
|
96
|
+
console.log(" 8) openai-compatible — custom OpenAI-compatible (vLLM, llama-cpp-server, ...)");
|
|
97
|
+
console.log(" 9) skip — keep existing values, just change defaults\n");
|
|
98
|
+
|
|
99
|
+
const sel = (await rl.question("Configure which provider? [1-9]: ")).trim();
|
|
96
100
|
const map: Record<string, ProviderChoice | "skip"> = {
|
|
97
101
|
"1": "anthropic", "2": "openai", "3": "gemini",
|
|
98
|
-
"4": "
|
|
99
|
-
"7": "
|
|
102
|
+
"4": "antigravity", "5": "kimi",
|
|
103
|
+
"6": "ollama", "7": "lmstudio", "8": "openai-compatible",
|
|
104
|
+
"9": "skip",
|
|
100
105
|
};
|
|
101
106
|
const choice = map[sel] ?? "skip";
|
|
102
107
|
|
|
@@ -108,7 +113,7 @@ export async function runSetupCommand(): Promise<void> {
|
|
|
108
113
|
next.providers = next.providers || {};
|
|
109
114
|
next.oauth = next.oauth || {};
|
|
110
115
|
|
|
111
|
-
if (choice === "anthropic" || choice === "openai" || choice === "gemini") {
|
|
116
|
+
if (choice === "anthropic" || choice === "openai" || choice === "gemini" || choice === "antigravity" || choice === "kimi") {
|
|
112
117
|
const authMode = (
|
|
113
118
|
await rl.question("Auth mode: (b)rowser OAuth login, (t)oken paste, or api (k)ey? [b]: ")
|
|
114
119
|
).trim().toLowerCase();
|
|
@@ -223,7 +228,7 @@ export async function runSetupCommand(): Promise<void> {
|
|
|
223
228
|
rl.close();
|
|
224
229
|
|
|
225
230
|
// Drop empty oauth/providers to keep config tidy.
|
|
226
|
-
if (next.oauth &&
|
|
231
|
+
if (next.oauth && Object.keys(next.oauth).length === 0) delete next.oauth;
|
|
227
232
|
|
|
228
233
|
await saveGlobalConfig(next);
|
|
229
234
|
console.log("\n[SUCCESS] Configuration saved to ~/.jeo/config.json");
|
|
@@ -9,6 +9,8 @@ import { companyLabel } from "../ai/model-catalog";
|
|
|
9
9
|
import { ProviderHttpError, ProviderStreamError } from "../ai/providers/errors";
|
|
10
10
|
import { AgyCliError } from "../agent/antigravity-cli";
|
|
11
11
|
import { sanitizeForTerminal } from "../tui/components/code-view";
|
|
12
|
+
import { OAUTH_PROVIDERS, API_KEY_ONLY_PROVIDERS } from "../auth/storage";
|
|
13
|
+
import { openaiCompatDef, allCompatNames } from "../ai/providers/openai-compatible-catalog";
|
|
12
14
|
// Re-export: engine.ts and callers import the refusal predicate from here; the
|
|
13
15
|
// definition lives in retry.ts so defaultRetryable can fail fast without a cycle.
|
|
14
16
|
export { isRefusalError } from "./retry";
|
|
@@ -72,6 +74,12 @@ function retryAfterMsOf(err: unknown): number | undefined {
|
|
|
72
74
|
return err instanceof ProviderHttpError ? err.retryAfterMs : undefined;
|
|
73
75
|
}
|
|
74
76
|
|
|
77
|
+
function authProviderForLabel(label: string): string | undefined {
|
|
78
|
+
const normalized = label.toLowerCase() === "openai codex" ? "openai" : label.toLowerCase();
|
|
79
|
+
const matches = (id: string) => id === normalized || companyLabel(id).toLowerCase() === normalized;
|
|
80
|
+
return OAUTH_PROVIDERS.find(matches) ?? API_KEY_ONLY_PROVIDERS.find(matches) ?? allCompatNames().find(matches);
|
|
81
|
+
}
|
|
82
|
+
|
|
75
83
|
export function friendlyProviderError(err: unknown): string {
|
|
76
84
|
if (err instanceof AgyCliError) return sanitizeForTerminal(err.message);
|
|
77
85
|
if (err instanceof ConnectionContextError) {
|
|
@@ -92,7 +100,10 @@ export function friendlyProviderError(err: unknown): string {
|
|
|
92
100
|
// Tencent/groq that speak Anthropic's/OpenAI's wire protocol — see
|
|
93
101
|
// `relabelProviderError`); only guess from message text for errors that never
|
|
94
102
|
// carried a `.provider` field (bare Error, a re-wrapped doneReason string, …).
|
|
95
|
-
const
|
|
103
|
+
const reportedProvider = knownErrorProvider(err) ?? /^(?:Error:\s*)?([^\r\n]+?) (?:request failed|stream failed|rejected the credential)\b/i.exec(msg)?.[1] ?? /^(?:Error:\s*)?(OpenAI(?: Codex)?|Antigravity|Anthropic|Gemini|Google)\b/i.exec(msg)?.[1];
|
|
104
|
+
// A raw prefix may contain a secret, not a provider name. Only registry labels
|
|
105
|
+
// may survive redaction or the round trip through an engine's doneReason.
|
|
106
|
+
const provider = reportedProvider && authProviderForLabel(reportedProvider) ? reportedProvider : (
|
|
96
107
|
/antigravity/i.test(msg)
|
|
97
108
|
? "Antigravity"
|
|
98
109
|
: /anthropic/i.test(msg)
|
|
@@ -117,8 +128,28 @@ export function friendlyProviderError(err: unknown): string {
|
|
|
117
128
|
: "";
|
|
118
129
|
return `Rate limited by ${provider} (HTTP 429).${retry} Auto-retry cannot clear this window right now — slow your request rate, wait for the reset, or switch model with /model (a local ollama model never rate-limits).`;
|
|
119
130
|
}
|
|
120
|
-
|
|
121
|
-
|
|
131
|
+
const credentialStatus = status ?? Number(/\b(401|403)\b/.exec(msg)?.[1]);
|
|
132
|
+
if (credentialStatus === 401 || credentialStatus === 403) {
|
|
133
|
+
const authProvider = authProviderForLabel(provider);
|
|
134
|
+
let hint = "Check the configured credentials with 'jeo setup' or /provider.";
|
|
135
|
+
if (authProvider === "openai") {
|
|
136
|
+
hint = "For ChatGPT/Codex, run 'jeo auth login openai' or /provider login openai. For API/custom endpoints, check OPENAI_API_KEY and OPENAI_BASE_URL (config: providers.openai / openaiBaseUrl).";
|
|
137
|
+
} else if (authProvider === "gemini") {
|
|
138
|
+
hint = "Check GEMINI_API_KEY. For Google OAuth, select an antigravity/* model and run 'jeo auth login antigravity' or /provider login antigravity.";
|
|
139
|
+
} else if (authProvider === "antigravity") {
|
|
140
|
+
hint = "Run 'jeo auth login antigravity' or /provider login antigravity; a Gemini login only works when Cloud Code Assist authorizes that token.";
|
|
141
|
+
} else if (authProvider === "kimi") {
|
|
142
|
+
hint = "For Kimi Code, run 'jeo auth login kimi'. For the Moonshot API, check KIMI_API_KEY or set it with /provider key kimi <key>.";
|
|
143
|
+
} else if (authProvider && !OAUTH_PROVIDERS.some(id => id === authProvider)) {
|
|
144
|
+
const apiKeyEnv = openaiCompatDef(authProvider)?.apiKeyEnv ?? `${authProvider.toUpperCase()}_API_KEY`;
|
|
145
|
+
const command = API_KEY_ONLY_PROVIDERS.some(id => id === authProvider)
|
|
146
|
+
? ` Set the key with /provider key ${authProvider} <key> or 'jeo auth login ${authProvider} --token <key>'.`
|
|
147
|
+
: " Update the custom provider's configured credentials.";
|
|
148
|
+
hint = `Check ${apiKeyEnv} or providers.${authProvider}.${command}`;
|
|
149
|
+
} else if (authProvider) {
|
|
150
|
+
hint = `Run 'jeo auth login ${authProvider}' or /provider login ${authProvider}; for API keys, check providers.${authProvider}.`;
|
|
151
|
+
}
|
|
152
|
+
return `${provider} rejected the credential (HTTP ${credentialStatus}). Run 'jeo auth status'. ${hint}`;
|
|
122
153
|
}
|
|
123
154
|
// A persistent 5xx (server-side fault on the provider's backend) — `ProviderHttpError`
|
|
124
155
|
// already embeds the numeric status directly in its message ("… failed (HTTP 500) …",
|