jeo-code 0.1.0 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +160 -0
- package/README.ko.md +160 -0
- package/README.md +115 -297
- package/README.zh.md +160 -0
- package/package.json +11 -6
- package/scripts/install.sh +28 -28
- package/scripts/uninstall.sh +17 -15
- package/src/AGENTS.md +50 -0
- package/src/agent/AGENTS.md +49 -0
- package/src/agent/bash-fixups.ts +103 -0
- package/src/agent/compaction.ts +410 -19
- package/src/agent/config-schema.ts +119 -5
- package/src/agent/context-files.ts +314 -17
- package/src/agent/dev/AGENTS.md +36 -0
- package/src/agent/dev/advanced-analyzer.ts +12 -0
- package/src/agent/dev/evolution-bridge.ts +82 -0
- package/src/agent/dev/evolution-logger.ts +41 -0
- package/src/agent/dev/self-analysis.ts +64 -0
- package/src/agent/dev/self-improve.ts +24 -0
- package/src/agent/dev/spec-automation.ts +49 -0
- package/src/agent/engine.ts +808 -54
- package/src/agent/hooks.ts +273 -0
- package/src/agent/loop.ts +21 -1
- package/src/agent/memory.ts +201 -0
- package/src/agent/model-recency.ts +32 -0
- package/src/agent/output-minimizer.ts +108 -0
- package/src/agent/output-util.ts +64 -0
- package/src/agent/plan.ts +187 -0
- package/src/agent/seed.ts +52 -0
- package/src/agent/session.ts +235 -21
- package/src/agent/state.ts +286 -39
- package/src/agent/step-budget.ts +232 -0
- package/src/agent/subagents.ts +223 -26
- package/src/agent/task-tool.ts +272 -0
- package/src/agent/todo-tool.ts +87 -0
- package/src/agent/tokenizer.ts +117 -0
- package/src/agent/tool-registry.ts +54 -0
- package/src/agent/tools.ts +624 -103
- package/src/agent/web-search.ts +538 -0
- package/src/ai/AGENTS.md +44 -0
- package/src/ai/index.ts +1 -0
- package/src/ai/model-catalog-compat.ts +3 -1
- package/src/ai/model-catalog.ts +74 -9
- package/src/ai/model-discovery.ts +215 -17
- package/src/ai/model-manager.ts +346 -32
- package/src/ai/model-picker.ts +1 -1
- package/src/ai/model-registry.ts +4 -2
- package/src/ai/pricing.ts +84 -0
- package/src/ai/provider-registry.ts +23 -0
- package/src/ai/provider-status.ts +60 -16
- package/src/ai/providers/AGENTS.md +42 -0
- package/src/ai/providers/anthropic.ts +250 -31
- package/src/ai/providers/antigravity.ts +219 -0
- package/src/ai/providers/errors.ts +15 -1
- package/src/ai/providers/gemini.ts +196 -13
- package/src/ai/providers/ollama.ts +37 -7
- package/src/ai/providers/openai-responses.ts +173 -0
- package/src/ai/providers/openai.ts +64 -12
- package/src/ai/sse.ts +4 -1
- package/src/ai/types.ts +18 -1
- package/src/auth/AGENTS.md +41 -0
- package/src/auth/callback-server.ts +6 -1
- package/src/auth/flows/AGENTS.md +32 -0
- package/src/auth/flows/antigravity.ts +151 -0
- package/src/auth/flows/google-project.ts +190 -0
- package/src/auth/flows/google.ts +39 -18
- package/src/auth/flows/index.ts +15 -5
- package/src/auth/flows/openai.ts +2 -2
- package/src/auth/oauth.ts +8 -0
- package/src/auth/refresh.ts +44 -27
- package/src/auth/storage.ts +149 -26
- package/src/auth/types.ts +1 -1
- package/src/autopilot.ts +362 -0
- package/src/bun-imports.d.ts +4 -0
- package/src/cli/AGENTS.md +39 -0
- package/src/cli/runner.ts +148 -14
- package/src/cli.ts +13 -4
- package/src/commands/AGENTS.md +40 -0
- package/src/commands/approve.ts +62 -3
- package/src/commands/auth.ts +167 -25
- package/src/commands/chat.ts +37 -8
- package/src/commands/deep-interview.ts +633 -175
- package/src/commands/doctor.ts +84 -37
- package/src/commands/evolve-core.ts +18 -0
- package/src/commands/evolve.ts +2 -1
- package/src/commands/export.ts +176 -0
- package/src/commands/gjc.ts +52 -0
- package/src/commands/launch.ts +3549 -240
- package/src/commands/mcp.ts +3 -3
- package/src/commands/ooo-seed.ts +19 -0
- package/src/commands/ralplan.ts +253 -35
- package/src/commands/resume.ts +1 -1
- package/src/commands/session.ts +183 -0
- package/src/commands/setup-helpers.ts +10 -3
- package/src/commands/setup.ts +57 -16
- package/src/commands/skills.ts +78 -18
- package/src/commands/state.ts +198 -0
- package/src/commands/status.ts +84 -0
- package/src/commands/team.ts +340 -212
- package/src/commands/ultragoal.ts +122 -61
- package/src/commands/update.ts +244 -0
- package/src/ledger.ts +270 -0
- package/src/mcp/AGENTS.md +38 -0
- package/src/mcp/server.ts +115 -14
- package/src/mcp/tools.ts +42 -22
- package/src/md-modules.d.ts +4 -0
- package/src/prompts/AGENTS.md +41 -0
- package/src/prompts/agents/AGENTS.md +35 -0
- package/src/prompts/agents/architect.md +35 -0
- package/src/prompts/agents/critic.md +37 -0
- package/src/prompts/agents/executor.md +36 -0
- package/src/prompts/agents/planner.md +37 -0
- package/src/prompts/skills/AGENTS.md +36 -0
- package/src/prompts/skills/deep-dive/AGENTS.md +31 -0
- package/src/prompts/skills/deep-dive/SKILL.md +13 -0
- package/src/prompts/skills/deep-interview/AGENTS.md +31 -0
- package/src/prompts/skills/deep-interview/SKILL.md +12 -0
- package/src/prompts/skills/gjc/AGENTS.md +31 -0
- package/src/prompts/skills/gjc/SKILL.md +15 -0
- package/src/prompts/skills/ralplan/AGENTS.md +31 -0
- package/src/prompts/skills/ralplan/SKILL.md +11 -0
- package/src/prompts/skills/team/AGENTS.md +31 -0
- package/src/prompts/skills/team/SKILL.md +11 -0
- package/src/prompts/skills/ultragoal/AGENTS.md +31 -0
- package/src/prompts/skills/ultragoal/SKILL.md +11 -0
- package/src/skills/AGENTS.md +38 -0
- package/src/skills/catalog.ts +565 -31
- package/src/tui/AGENTS.md +43 -0
- package/src/tui/app.ts +1181 -92
- package/src/tui/components/AGENTS.md +42 -0
- package/src/tui/components/ascii-art.ts +257 -15
- package/src/tui/components/autocomplete.ts +98 -16
- package/src/tui/components/autopilot-status.ts +65 -0
- package/src/tui/components/category-index.ts +49 -0
- package/src/tui/components/code-view.ts +54 -11
- package/src/tui/components/color.ts +171 -2
- package/src/tui/components/config-panel.ts +82 -15
- package/src/tui/components/duration.ts +38 -0
- package/src/tui/components/evolution.ts +3 -3
- package/src/tui/components/footer.ts +91 -42
- package/src/tui/components/forge.ts +426 -31
- package/src/tui/components/hints.ts +54 -0
- package/src/tui/components/hud.ts +73 -0
- package/src/tui/components/index.ts +4 -0
- package/src/tui/components/input-box.ts +150 -0
- package/src/tui/components/layout.ts +11 -3
- package/src/tui/components/live-model-picker.ts +108 -0
- package/src/tui/components/markdown-table.ts +140 -0
- package/src/tui/components/markdown-text.ts +97 -0
- package/src/tui/components/meter.ts +4 -1
- package/src/tui/components/model-picker.ts +3 -2
- package/src/tui/components/provider-picker.ts +3 -2
- package/src/tui/components/section.ts +70 -0
- package/src/tui/components/select-list.ts +40 -10
- package/src/tui/components/skill-picker.ts +25 -0
- package/src/tui/components/slash.ts +244 -21
- package/src/tui/components/status.ts +272 -11
- package/src/tui/components/step-timeline.ts +218 -0
- package/src/tui/components/stream.ts +26 -9
- package/src/tui/components/themes.ts +212 -6
- package/src/tui/components/todo-card.ts +47 -0
- package/src/tui/components/tool-list.ts +58 -12
- package/src/tui/components/transcript.ts +120 -0
- package/src/tui/components/update-box.ts +31 -0
- package/src/tui/components/welcome.ts +162 -0
- package/src/tui/components/width.ts +163 -0
- package/src/tui/monitoring/AGENTS.md +31 -0
- package/src/tui/monitoring/hud-view.ts +55 -0
- package/src/tui/renderer.ts +112 -3
- package/src/tui/terminal.ts +40 -33
- package/src/util/AGENTS.md +39 -0
- package/src/util/clipboard-image.ts +118 -0
- package/src/util/env.ts +12 -0
- package/src/util/provider-error.ts +78 -0
- package/src/util/retry.ts +91 -6
- package/src/util/update-check.ts +64 -0
- package/src/commands/models.ts +0 -104
package/src/ai/model-catalog.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Static model catalog — capability metadata for well-known public models, so
|
|
3
3
|
* the TUI can show context window, max output, supported thinking levels, and
|
|
4
|
-
* image support next to a model
|
|
5
|
-
*
|
|
6
|
-
* not a copy of any vendor's catalog source. Live discovery
|
|
4
|
+
* image support next to a model. This is factual capability data about public
|
|
5
|
+
* models, not a copy of any vendor's catalog source. Live discovery
|
|
7
6
|
* (`model-discovery.ts`) remains the source of truth for *availability*; this
|
|
8
7
|
* catalog annotates known ids with capabilities.
|
|
9
8
|
*/
|
|
@@ -14,7 +13,7 @@ export type ThinkLevel = "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
|
14
13
|
export const THINK_LEVELS: readonly ThinkLevel[] = ["minimal", "low", "medium", "high", "xhigh"];
|
|
15
14
|
|
|
16
15
|
export interface CatalogModel {
|
|
17
|
-
/**
|
|
16
|
+
/** jeo-facing canonical id (what a user types). */
|
|
18
17
|
canonical: string;
|
|
19
18
|
provider: ProviderName;
|
|
20
19
|
/** Exact provider model id used on the wire. */
|
|
@@ -27,19 +26,39 @@ export interface CatalogModel {
|
|
|
27
26
|
thinking: ThinkLevel[];
|
|
28
27
|
/** Whether the model accepts image input. */
|
|
29
28
|
images: boolean;
|
|
29
|
+
/** Optional company override. */
|
|
30
|
+
company?: string;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
const FULL: ThinkLevel[] = ["minimal", "low", "medium", "high", "xhigh"];
|
|
33
34
|
const STD: ThinkLevel[] = ["minimal", "low", "medium", "high"];
|
|
34
35
|
|
|
36
|
+
export const ANTIGRAVITY_MODELS = [
|
|
37
|
+
"claude-opus-4-5-thinking",
|
|
38
|
+
"claude-opus-4-6-thinking",
|
|
39
|
+
"claude-sonnet-4-5",
|
|
40
|
+
"claude-sonnet-4-5-thinking",
|
|
41
|
+
"claude-sonnet-4-6",
|
|
42
|
+
"claude-sonnet-4-6-thinking",
|
|
43
|
+
"gemini-2.5-flash",
|
|
44
|
+
"gemini-2.5-flash-thinking",
|
|
45
|
+
"gemini-2.5-pro",
|
|
46
|
+
"gemini-3-flash",
|
|
47
|
+
"gemini-3-pro-high",
|
|
48
|
+
"gemini-3-pro-low",
|
|
49
|
+
"gemini-3.1-pro-high",
|
|
50
|
+
"gemini-3.1-pro-low",
|
|
51
|
+
"gpt-oss-120b-medium",
|
|
52
|
+
] as const;
|
|
53
|
+
|
|
35
54
|
/** A curated set of common public models with their documented capabilities. */
|
|
36
55
|
export const MODEL_CATALOG: readonly CatalogModel[] = [
|
|
37
56
|
// Anthropic
|
|
38
|
-
{ canonical: "claude-3-5-
|
|
39
|
-
{ canonical: "claude-
|
|
40
|
-
{ canonical: "claude-
|
|
41
|
-
{ canonical: "claude-
|
|
42
|
-
{ canonical: "claude-opus-4", provider: "anthropic", providerModel: "claude-opus-4-
|
|
57
|
+
{ canonical: "claude-3-5-sonnet", provider: "anthropic", providerModel: "claude-3-5-sonnet-20241022", contextTokens: 200_000, maxOutputTokens: 8192, thinking: [], images: true },
|
|
58
|
+
{ canonical: "claude-haiku-4-5", provider: "anthropic", providerModel: "claude-haiku-4-5-20251001", contextTokens: 200_000, maxOutputTokens: 64_000, thinking: FULL, images: true },
|
|
59
|
+
{ canonical: "claude-sonnet-4-5", provider: "anthropic", providerModel: "claude-sonnet-4-5-20250929", contextTokens: 200_000, maxOutputTokens: 64_000, thinking: FULL, images: true },
|
|
60
|
+
{ canonical: "claude-opus-4-1", provider: "anthropic", providerModel: "claude-opus-4-1-20250805", contextTokens: 200_000, maxOutputTokens: 32_000, thinking: FULL, images: true },
|
|
61
|
+
{ canonical: "claude-opus-4-5", provider: "anthropic", providerModel: "claude-opus-4-5-20251101", contextTokens: 200_000, maxOutputTokens: 64_000, thinking: FULL, images: true },
|
|
43
62
|
// OpenAI
|
|
44
63
|
{ canonical: "gpt-4o", provider: "openai", providerModel: "gpt-4o", contextTokens: 128_000, maxOutputTokens: 16_384, thinking: [], images: true },
|
|
45
64
|
{ canonical: "gpt-4o-mini", provider: "openai", providerModel: "gpt-4o-mini", contextTokens: 128_000, maxOutputTokens: 16_384, thinking: [], images: true },
|
|
@@ -47,15 +66,37 @@ export const MODEL_CATALOG: readonly CatalogModel[] = [
|
|
|
47
66
|
{ canonical: "o3", provider: "openai", providerModel: "o3", contextTokens: 200_000, maxOutputTokens: 100_000, thinking: STD, images: true },
|
|
48
67
|
{ canonical: "o3-mini", provider: "openai", providerModel: "o3-mini", contextTokens: 200_000, maxOutputTokens: 100_000, thinking: STD, images: false },
|
|
49
68
|
{ canonical: "o4-mini", provider: "openai", providerModel: "o4-mini", contextTokens: 200_000, maxOutputTokens: 100_000, thinking: STD, images: true },
|
|
69
|
+
{ canonical: "gpt-5.5", provider: "openai", providerModel: "gpt-5.5", contextTokens: 400_000, maxOutputTokens: 128_000, thinking: FULL, images: true },
|
|
70
|
+
{ canonical: "gpt-5.4", provider: "openai", providerModel: "gpt-5.4", contextTokens: 400_000, maxOutputTokens: 128_000, thinking: FULL, images: true },
|
|
50
71
|
// Google
|
|
51
72
|
{ canonical: "gemini-1.5-pro", provider: "gemini", providerModel: "gemini-1.5-pro", contextTokens: 1_000_000, maxOutputTokens: 8_192, thinking: [], images: true },
|
|
52
73
|
{ canonical: "gemini-2.0-flash", provider: "gemini", providerModel: "gemini-2.0-flash", contextTokens: 1_000_000, maxOutputTokens: 8_192, thinking: [], images: true },
|
|
53
74
|
{ canonical: "gemini-2.5-flash", provider: "gemini", providerModel: "gemini-2.5-flash", contextTokens: 1_000_000, maxOutputTokens: 65_536, thinking: STD, images: true },
|
|
54
75
|
{ canonical: "gemini-2.5-pro", provider: "gemini", providerModel: "gemini-2.5-pro", contextTokens: 1_000_000, maxOutputTokens: 65_536, thinking: STD, images: true },
|
|
76
|
+
// Google Antigravity / Gemini CLI (Cloud Code Assist) — provider-qualified to avoid
|
|
77
|
+
// collisions with public Gemini, Anthropic, and OpenAI/Codex ids.
|
|
78
|
+
...ANTIGRAVITY_MODELS.map((id): CatalogModel => ({
|
|
79
|
+
canonical: `antigravity/${id}`,
|
|
80
|
+
provider: "antigravity",
|
|
81
|
+
providerModel: id,
|
|
82
|
+
contextTokens: id.includes("claude") ? 200_000 : id.includes("gemini-3") ? 1_000_000 : 1_000_000,
|
|
83
|
+
maxOutputTokens: id.includes("claude") ? 64_000 : 65_536,
|
|
84
|
+
thinking: id.includes("thinking") || id.includes("-high") || id.includes("-low") || id.includes("gemini-3") ? FULL : STD,
|
|
85
|
+
images: !id.includes("gpt-oss"),
|
|
86
|
+
company: id.includes("claude") ? "Anthropic via Antigravity" : id.includes("gpt") ? "OpenAI via Antigravity" : "Google Antigravity",
|
|
87
|
+
})),
|
|
55
88
|
// Ollama (local)
|
|
56
89
|
{ canonical: "qwen2.5", provider: "ollama", providerModel: "ollama/qwen2.5:0.5b", contextTokens: 32_768, maxOutputTokens: 8_192, thinking: [], images: false },
|
|
57
90
|
];
|
|
58
91
|
|
|
92
|
+
/**
|
|
93
|
+
* OpenAI models the ChatGPT/Codex subscription backend (`codex/responses`) actually
|
|
94
|
+
* serves. The Codex backend rejects standard API ids (gpt-4o, o3, …) and exposes no
|
|
95
|
+
* usable list endpoint, so an OAuth-only OpenAI login surfaces exactly these instead
|
|
96
|
+
* of the full chat-completions catalog. Verified live against a ChatGPT account.
|
|
97
|
+
*/
|
|
98
|
+
export const CODEX_MODELS: readonly string[] = ["gpt-5.5", "gpt-5.4"];
|
|
99
|
+
|
|
59
100
|
/** Format a token count compactly (1000 → 1K, 1_000_000 → 1M). */
|
|
60
101
|
export function formatTokens(n: number): string {
|
|
61
102
|
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(n % 1_000_000 === 0 ? 0 : 1)}M`;
|
|
@@ -69,6 +110,18 @@ export function findCatalogModel(idOrModel: string): CatalogModel | undefined {
|
|
|
69
110
|
return MODEL_CATALOG.find(m => m.canonical === q || m.providerModel === q || `${m.provider}/${m.providerModel}` === q);
|
|
70
111
|
}
|
|
71
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Map a user-facing canonical id to the exact provider model id used on the wire
|
|
115
|
+
* (e.g. `claude-3-5-sonnet` → `claude-3-5-sonnet-20241022`). Ids that are not a
|
|
116
|
+
* known canonical (already a provider id, a live-discovered id, an alias target)
|
|
117
|
+
* are returned unchanged. Scope to `provider` when known so a canonical never
|
|
118
|
+
* leaks across providers.
|
|
119
|
+
*/
|
|
120
|
+
export function toProviderModel(id: string, provider?: ProviderName): string {
|
|
121
|
+
const m = MODEL_CATALOG.find(c => c.canonical === id && (!provider || c.provider === provider));
|
|
122
|
+
return m ? m.providerModel : id;
|
|
123
|
+
}
|
|
124
|
+
|
|
72
125
|
/** Case-insensitive substring match over canonical + provider model id. */
|
|
73
126
|
export function fuzzyMatchCatalog(query: string): CatalogModel[] {
|
|
74
127
|
const q = query.trim().toLowerCase();
|
|
@@ -95,3 +148,15 @@ export function supportsThinking(modelId: string, level: ThinkLevel): boolean {
|
|
|
95
148
|
const meta = catalogMetadata(modelId);
|
|
96
149
|
return meta ? meta.thinking.includes(level) : false;
|
|
97
150
|
}
|
|
151
|
+
export function companyLabel(provider: string, entry?: { company?: string }): string {
|
|
152
|
+
if (entry?.company) {
|
|
153
|
+
return entry.company;
|
|
154
|
+
}
|
|
155
|
+
const low = provider.toLowerCase();
|
|
156
|
+
if (low === "anthropic") return "Anthropic";
|
|
157
|
+
if (low === "openai") return "OpenAI";
|
|
158
|
+
if (low === "gemini") return "Google";
|
|
159
|
+
if (low === "ollama") return "Ollama";
|
|
160
|
+
if (low === "antigravity") return "Antigravity";
|
|
161
|
+
return provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
162
|
+
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Live model discovery — query a provider's `models` endpoint with the resolved
|
|
3
3
|
* credential (OAuth bearer or API key) and return the model ids the account can
|
|
4
|
-
* actually use. This powers the TUI `/
|
|
5
|
-
*
|
|
6
|
-
* static alias guess.
|
|
4
|
+
* actually use. This powers the TUI `/model` and `/provider` flows, so users
|
|
5
|
+
* pick from the real, logged-in catalog instead of a static alias guess.
|
|
7
6
|
*
|
|
8
7
|
* Network access is injectable (`fetchImpl`) and every call is timeout-bounded so
|
|
9
8
|
* the TUI never hangs; failures degrade to a tagged result, never a throw.
|
|
@@ -12,6 +11,8 @@ import { readGlobalConfig, type Config } from "../agent/state";
|
|
|
12
11
|
import { resolveCredential, type AuthProvider, type Credential } from "../auth";
|
|
13
12
|
import type { ProviderName } from "./types";
|
|
14
13
|
import { PROVIDER_NAMES } from "./provider-status";
|
|
14
|
+
import { catalogByProvider, CODEX_MODELS } from "./model-catalog";
|
|
15
|
+
import { extractChatgptAccountId } from "./providers/openai-responses";
|
|
15
16
|
|
|
16
17
|
export interface ProviderModelsResult {
|
|
17
18
|
provider: ProviderName;
|
|
@@ -22,6 +23,8 @@ export interface ProviderModelsResult {
|
|
|
22
23
|
source: "oauth" | "api_key" | "keyless" | "none";
|
|
23
24
|
/** Present on failure: a short, human-readable reason. */
|
|
24
25
|
error?: string;
|
|
26
|
+
/** True when the live endpoint was unusable and ids came from the static catalog. */
|
|
27
|
+
fallback?: boolean;
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
export interface DiscoveryOptions {
|
|
@@ -33,10 +36,26 @@ export interface DiscoveryOptions {
|
|
|
33
36
|
signal?: AbortSignal;
|
|
34
37
|
/** Cap the number of returned ids per provider; default 100. */
|
|
35
38
|
limit?: number;
|
|
39
|
+
/** Config snapshot used for provider base URLs. */
|
|
40
|
+
config?: Config;
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
const DEFAULT_TIMEOUT = 5000;
|
|
39
44
|
const DEFAULT_LIMIT = 100;
|
|
45
|
+
// The Codex models endpoint REQUIRES `client_version` (HTTP 400 without it) and
|
|
46
|
+
// GATES the list by it — old versions get `{"models":[]}`. Keep this high enough
|
|
47
|
+
// to receive the full current list (verified live 2026-06-12: 0.46→[], 0.99→gpt-5.4,
|
|
48
|
+
// 1.0/2.0→full gpt-5.5 set). On drift the catalog fallback keeps Codex usable.
|
|
49
|
+
const CODEX_CLIENT_VERSION = "2.0.0";
|
|
50
|
+
const CODEX_MODELS_URL = `https://chatgpt.com/backend-api/codex/models?client_version=${CODEX_CLIENT_VERSION}`;
|
|
51
|
+
const ANTIGRAVITY_MODELS_URL = "https://daily-cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels";
|
|
52
|
+
const ANTIGRAVITY_MODEL_DENYLIST = new Set([
|
|
53
|
+
"chat_20706",
|
|
54
|
+
"chat_23310",
|
|
55
|
+
"gemini-2.5-flash-thinking",
|
|
56
|
+
"gemini-3-pro-low",
|
|
57
|
+
"gemini-2.5-pro",
|
|
58
|
+
]);
|
|
40
59
|
|
|
41
60
|
function anthropicHeaders(cred: Credential): Record<string, string> {
|
|
42
61
|
if (cred.kind === "oauth") {
|
|
@@ -48,28 +67,55 @@ function anthropicHeaders(cred: Credential): Record<string, string> {
|
|
|
48
67
|
return {};
|
|
49
68
|
}
|
|
50
69
|
|
|
70
|
+
function authProviderFor(provider: ProviderName): AuthProvider | undefined {
|
|
71
|
+
if (provider === "ollama") return undefined;
|
|
72
|
+
return provider;
|
|
73
|
+
}
|
|
74
|
+
|
|
51
75
|
/** Build the discovery request (url + headers) for a provider/credential. */
|
|
52
76
|
export function discoveryRequest(
|
|
53
77
|
provider: ProviderName,
|
|
54
78
|
cred: Credential | undefined,
|
|
55
79
|
baseUrl?: string,
|
|
56
|
-
): { url: string; headers: Record<string, string
|
|
80
|
+
): { url: string; headers: Record<string, string>; method?: "GET" | "POST"; body?: string } {
|
|
57
81
|
switch (provider) {
|
|
58
82
|
case "anthropic":
|
|
59
83
|
return { url: "https://api.anthropic.com/v1/models", headers: anthropicHeaders(cred!) };
|
|
60
84
|
case "openai": {
|
|
61
|
-
const base = (baseUrl ?? process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1").replace(/\/$/, "");
|
|
62
85
|
const token = cred?.kind === "oauth" || cred?.kind === "api_key" ? cred.token : "";
|
|
86
|
+
if (cred?.kind === "oauth" && !baseUrl && !process.env.OPENAI_BASE_URL) {
|
|
87
|
+
const accountId = extractChatgptAccountId(token);
|
|
88
|
+
const headers: Record<string, string> = {
|
|
89
|
+
Authorization: `Bearer ${token}`,
|
|
90
|
+
"OpenAI-Beta": "responses=experimental",
|
|
91
|
+
originator: "codex_cli_rs",
|
|
92
|
+
};
|
|
93
|
+
if (accountId) headers["chatgpt-account-id"] = accountId;
|
|
94
|
+
return { url: CODEX_MODELS_URL, headers };
|
|
95
|
+
}
|
|
96
|
+
const base = (baseUrl ?? process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1").replace(/\/$/, "");
|
|
63
97
|
return { url: `${base}/models`, headers: token ? { Authorization: `Bearer ${token}` } : {} };
|
|
64
98
|
}
|
|
65
99
|
case "gemini": {
|
|
66
100
|
const oauth = cred?.kind === "oauth" ? cred.token : undefined;
|
|
67
101
|
const apiKey = cred?.kind === "api_key" ? cred.token : undefined;
|
|
102
|
+
// pageSize=1000: the DEFAULT page is 50 models WITH a nextPageToken — the
|
|
103
|
+
// single-shot fetch silently dropped everything past page 1 (verified live:
|
|
104
|
+
// 50+token vs 55 total). listProviderModels also follows nextPageToken.
|
|
68
105
|
const url = oauth
|
|
69
|
-
? "https://generativelanguage.googleapis.com/v1beta/models"
|
|
70
|
-
: `https://generativelanguage.googleapis.com/v1beta/models?key=${apiKey ?? ""}`;
|
|
106
|
+
? "https://generativelanguage.googleapis.com/v1beta/models?pageSize=1000"
|
|
107
|
+
: `https://generativelanguage.googleapis.com/v1beta/models?pageSize=1000&key=${apiKey ?? ""}`;
|
|
71
108
|
return { url, headers: oauth ? { authorization: `Bearer ${oauth}` } : {} };
|
|
72
109
|
}
|
|
110
|
+
case "antigravity": {
|
|
111
|
+
const token = cred?.kind === "oauth" ? cred.token : "";
|
|
112
|
+
return {
|
|
113
|
+
url: ANTIGRAVITY_MODELS_URL,
|
|
114
|
+
headers: token ? { authorization: `Bearer ${token}`, "content-type": "application/json", "User-Agent": "antigravity/1.104.0" } : {},
|
|
115
|
+
method: "POST",
|
|
116
|
+
body: "{}",
|
|
117
|
+
};
|
|
118
|
+
}
|
|
73
119
|
case "ollama": {
|
|
74
120
|
const base = (baseUrl ?? "http://localhost:11434").replace(/\/$/, "");
|
|
75
121
|
return { url: `${base}/api/tags`, headers: {} };
|
|
@@ -77,17 +123,123 @@ export function discoveryRequest(
|
|
|
77
123
|
}
|
|
78
124
|
}
|
|
79
125
|
|
|
80
|
-
/**
|
|
126
|
+
/**
|
|
127
|
+
* OpenAI `/v1/models` lists every model family — embeddings, audio/tts, image, moderation,
|
|
128
|
+
* realtime — but jeo only calls chat/completions. Drop the families that can never serve a
|
|
129
|
+
* chat turn so pickers never offer a model that fails at call time.
|
|
130
|
+
*/
|
|
131
|
+
function isOpenAiChatModel(id: string): boolean {
|
|
132
|
+
return !/(^|[-/])(text-embedding|embedding|tts|whisper|dall-e|moderation|omni-moderation|davinci|babbage|computer-use|realtime|audio|image|sora|transcribe|instruct|codex)([-/]|$)/i.test(id);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Gemini exposes generateContent for image/tts/embedding variants too, but those emit
|
|
137
|
+
* audio/image/vectors — not a usable text turn for a coding chat. Drop them by family.
|
|
138
|
+
*/
|
|
139
|
+
function isGeminiChatModel(id: string): boolean {
|
|
140
|
+
return !/(^|[-/])(embedding|aqa|tts|image|imagen|veo|lyria|nano-banana|deep-research|computer-use|antigravity)([-/]|$)/i.test(id);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
type CodexModelRow = { slug?: string; id?: string; supported_in_api?: boolean; priority?: number };
|
|
144
|
+
type AntigravityModelRow = { slug?: string; id?: string; name?: string; isInternal?: boolean; model?: string };
|
|
145
|
+
|
|
146
|
+
/** Parse a provider's models response body into normalized, chat-capable model ids. */
|
|
81
147
|
export function parseModelsBody(provider: ProviderName, body: unknown): string[] {
|
|
82
|
-
const data = body as {
|
|
148
|
+
const data = body as {
|
|
149
|
+
data?: { id?: string }[];
|
|
150
|
+
models?: ({ name?: string; supportedGenerationMethods?: string[] } & CodexModelRow)[];
|
|
151
|
+
};
|
|
83
152
|
if (provider === "ollama") {
|
|
84
153
|
return (data.models ?? []).map(m => `ollama/${m.name ?? ""}`).filter(s => s !== "ollama/");
|
|
85
154
|
}
|
|
155
|
+
if (provider === "antigravity") {
|
|
156
|
+
// fetchAvailableModels keys the map by the CALLABLE model id (e.g.
|
|
157
|
+
// "gemini-3-flash"); the entry's `model` field is an internal enum
|
|
158
|
+
// (MODEL_PLACEHOLDER_*) and must never be surfaced. The response's OWN
|
|
159
|
+
// metadata decides what to show — no hard-coded model lists:
|
|
160
|
+
// 1. `agentModelSorts` groups are the API's positive agent/chat set
|
|
161
|
+
// (exactly what Antigravity's model picker offers) — prefer it.
|
|
162
|
+
// 2. Otherwise fall back to excluding the API's non-chat role lists
|
|
163
|
+
// (tab completion, image generation, transcription, deprecations).
|
|
164
|
+
const payload = body as {
|
|
165
|
+
models?: Record<string, AntigravityModelRow> | AntigravityModelRow[];
|
|
166
|
+
agentModelSorts?: { groups?: { modelIds?: string[] }[] }[];
|
|
167
|
+
tabModelIds?: string[];
|
|
168
|
+
imageGenerationModelIds?: string[];
|
|
169
|
+
audioTranscriptionModelIds?: string[];
|
|
170
|
+
commitMessageModelIds?: string[];
|
|
171
|
+
mqueryModelIds?: string[];
|
|
172
|
+
/** Array of ids OR an object keyed by deprecated id. */
|
|
173
|
+
deprecatedModelIds?: string[] | Record<string, unknown>;
|
|
174
|
+
};
|
|
175
|
+
const roleIds = (v: unknown): string[] => (Array.isArray(v) ? v.filter((x): x is string => typeof x === "string") : []);
|
|
176
|
+
const deprecated = Array.isArray(payload.deprecatedModelIds)
|
|
177
|
+
? roleIds(payload.deprecatedModelIds)
|
|
178
|
+
: Object.keys(payload.deprecatedModelIds ?? {});
|
|
179
|
+
const agentIds = new Set(
|
|
180
|
+
(Array.isArray(payload.agentModelSorts) ? payload.agentModelSorts : [])
|
|
181
|
+
.flatMap(sort => (Array.isArray(sort?.groups) ? sort.groups : []))
|
|
182
|
+
.flatMap(group => roleIds(group?.modelIds)),
|
|
183
|
+
);
|
|
184
|
+
const nonChat = new Set([
|
|
185
|
+
...roleIds(payload.tabModelIds),
|
|
186
|
+
...roleIds(payload.imageGenerationModelIds),
|
|
187
|
+
...roleIds(payload.audioTranscriptionModelIds),
|
|
188
|
+
...roleIds(payload.commitMessageModelIds),
|
|
189
|
+
...roleIds(payload.mqueryModelIds),
|
|
190
|
+
...deprecated,
|
|
191
|
+
]);
|
|
192
|
+
const rawModels = payload.models;
|
|
193
|
+
const ids = Array.isArray(rawModels)
|
|
194
|
+
? rawModels.map(m => m.slug ?? m.id ?? m.name ?? "").filter(Boolean)
|
|
195
|
+
: Object.entries(rawModels ?? {})
|
|
196
|
+
.filter(([id, model]) =>
|
|
197
|
+
!ANTIGRAVITY_MODEL_DENYLIST.has(id) &&
|
|
198
|
+
model?.isInternal !== true &&
|
|
199
|
+
(agentIds.size > 0 ? agentIds.has(id) : !nonChat.has(id)))
|
|
200
|
+
.map(([id]) => id);
|
|
201
|
+
return ids
|
|
202
|
+
.map(id => id.replace(/^models\//, ""))
|
|
203
|
+
.filter(Boolean)
|
|
204
|
+
.map(id => id.startsWith("antigravity/") ? id : `antigravity/${id}`);
|
|
205
|
+
}
|
|
86
206
|
if (provider === "gemini") {
|
|
87
|
-
|
|
207
|
+
// Keep only models the generateContent endpoint can serve (skip embeddings/tts/aqa/etc).
|
|
208
|
+
// When the list omits supportedGenerationMethods, keep the id (be permissive).
|
|
209
|
+
return (data.models ?? [])
|
|
210
|
+
.filter(m => !m.supportedGenerationMethods || m.supportedGenerationMethods.includes("generateContent"))
|
|
211
|
+
.map(m => (m.name ?? "").replace(/^models\//, ""))
|
|
212
|
+
.filter(id => id && isGeminiChatModel(id));
|
|
213
|
+
}
|
|
214
|
+
if (provider === "openai" && data.models?.some(m => m.slug || m.id)) {
|
|
215
|
+
return data.models
|
|
216
|
+
.filter(m => m.supported_in_api !== false)
|
|
217
|
+
.map(m => m.slug ?? m.id ?? "")
|
|
218
|
+
// Review-only entries (e.g. codex-auto-review) are not chat-turn models.
|
|
219
|
+
.filter(id => id && !/(^|[-/])auto-review([-/]|$)/i.test(id));
|
|
88
220
|
}
|
|
89
221
|
// anthropic / openai: { data: [{ id }] }
|
|
90
|
-
|
|
222
|
+
const ids = (data.data ?? []).map(m => m.id ?? "").filter(Boolean);
|
|
223
|
+
return provider === "openai" ? ids.filter(isOpenAiChatModel) : ids;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* When a provider is authenticated (oauth/api_key) but the live `models` endpoint
|
|
228
|
+
* is unusable — e.g. ChatGPT/Codex OAuth tokens are rejected by `api.openai.com/v1/models`
|
|
229
|
+
* — surface the static catalog ids so the provider's models still appear in pickers.
|
|
230
|
+
* Keyless/not-logged-in results are returned unchanged.
|
|
231
|
+
*/
|
|
232
|
+
export function catalogOr(result: ProviderModelsResult): ProviderModelsResult {
|
|
233
|
+
if (result.ok && result.models.length > 0) return result;
|
|
234
|
+
// OpenAI/Codex OAuth legitimately rejects the standard /models endpoint while
|
|
235
|
+
// the fixed Codex ids still work for calls. Other OAuth providers may fall
|
|
236
|
+
// back to their static catalog too — EXCEPT Antigravity, whose available
|
|
237
|
+
// models depend on the Cloud Code Assist agent backend and must not be faked.
|
|
238
|
+
if (result.source !== "oauth") return result;
|
|
239
|
+
if (result.provider === "antigravity") return result;
|
|
240
|
+
const ids = result.provider === "openai" ? [...CODEX_MODELS] : catalogByProvider(result.provider).map(m => m.providerModel);
|
|
241
|
+
if (ids.length === 0) return result;
|
|
242
|
+
return { ...result, models: ids, ok: true, fallback: true };
|
|
91
243
|
}
|
|
92
244
|
|
|
93
245
|
/** Discover the live model list for one provider. Never throws. */
|
|
@@ -101,25 +253,68 @@ export async function listProviderModels(
|
|
|
101
253
|
let cred: Credential | undefined;
|
|
102
254
|
let source: ProviderModelsResult["source"] = "keyless";
|
|
103
255
|
if (provider !== "ollama") {
|
|
104
|
-
|
|
256
|
+
const authProvider = authProviderFor(provider);
|
|
257
|
+
const raw = await resolveCredential(authProvider!);
|
|
258
|
+
cred = raw;
|
|
105
259
|
source = cred.kind === "oauth" ? "oauth" : cred.kind === "api_key" ? "api_key" : "none";
|
|
260
|
+
const config = opts.config ?? (await readGlobalConfig());
|
|
261
|
+
|
|
262
|
+
if (provider === "antigravity") {
|
|
263
|
+
// Antigravity lists models from the LIVE Cloud Code Assist endpoint
|
|
264
|
+
// (v1internal:fetchAvailableModels) — never from a hard-coded catalog.
|
|
265
|
+
// A gemini-cli OAuth token is tried as a fallback credential for the
|
|
266
|
+
// list call; if the backend rejects it the failure is surfaced honestly.
|
|
267
|
+
if (cred.kind !== "oauth") {
|
|
268
|
+
const gemini = await resolveCredential("gemini");
|
|
269
|
+
if (gemini.kind === "oauth") {
|
|
270
|
+
cred = gemini;
|
|
271
|
+
source = "oauth";
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (cred.kind !== "oauth") {
|
|
275
|
+
return { provider, models: [], ok: false, source, error: "not logged in with Antigravity OAuth" };
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const prov = authProvider!;
|
|
280
|
+
// Antigravity's list endpoint accepts ONLY OAuth (the request builder sends
|
|
281
|
+
// no api-key header), so never swap its credential to an api_key.
|
|
282
|
+
if (provider !== "antigravity" && cred.kind === "oauth" && config.providers?.[prov]) {
|
|
283
|
+
// An API key is the broader, documented path — prefer it for live discovery.
|
|
284
|
+
cred = { kind: "api_key", provider: prov, token: config.providers[prov]! };
|
|
285
|
+
source = "api_key";
|
|
286
|
+
}
|
|
106
287
|
const isLocalOpenAi = provider === "openai" && !!(opts.baseUrl ?? process.env.OPENAI_BASE_URL);
|
|
107
288
|
if (source === "none" && !isLocalOpenAi) {
|
|
108
289
|
return { provider, models: [], ok: false, source, error: "not logged in" };
|
|
109
290
|
}
|
|
110
291
|
}
|
|
111
292
|
|
|
112
|
-
const { url, headers } = discoveryRequest(provider, cred, opts.baseUrl);
|
|
293
|
+
const { url, headers, method, body: requestBody } = discoveryRequest(provider, cred, opts.baseUrl);
|
|
113
294
|
const timeout = opts.timeoutMs ?? DEFAULT_TIMEOUT;
|
|
114
295
|
const signal = opts.signal ?? AbortSignal.timeout(timeout);
|
|
115
296
|
try {
|
|
116
|
-
const res = await fetchImpl(url, { headers, signal });
|
|
297
|
+
const res = await fetchImpl(url, { method: method ?? "GET", headers, body: requestBody, signal });
|
|
117
298
|
if (!res.ok) {
|
|
118
299
|
const reason = res.status === 401 || res.status === 403 ? "auth rejected" : `HTTP ${res.status}`;
|
|
119
300
|
return { provider, models: [], ok: false, source, error: reason };
|
|
120
301
|
}
|
|
121
302
|
const body = await res.json();
|
|
122
|
-
|
|
303
|
+
let ids = parseModelsBody(provider, body);
|
|
304
|
+
// Gemini paginates: follow nextPageToken (bounded) so the available list is
|
|
305
|
+
// COMPLETE — page 1 alone silently dropped the newest models (round-15).
|
|
306
|
+
if (provider === "gemini") {
|
|
307
|
+
let pageToken = (body as { nextPageToken?: string })?.nextPageToken;
|
|
308
|
+
for (let page = 0; pageToken && page < 4; page++) {
|
|
309
|
+
const pagedUrl = `${url}&pageToken=${encodeURIComponent(pageToken)}`;
|
|
310
|
+
const pageRes = await fetchImpl(pagedUrl, { method: "GET", headers, signal });
|
|
311
|
+
if (!pageRes.ok) break; // partial list beats a hard failure
|
|
312
|
+
const pageBody = await pageRes.json() as { nextPageToken?: string };
|
|
313
|
+
ids = ids.concat(parseModelsBody(provider, pageBody));
|
|
314
|
+
pageToken = pageBody.nextPageToken;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
const models = [...new Set(ids)].sort().slice(0, limit);
|
|
123
318
|
return { provider, models, ok: true, source };
|
|
124
319
|
} catch (err) {
|
|
125
320
|
const msg = (err as Error)?.name === "TimeoutError" || (err as Error)?.name === "AbortError" ? "timeout" : "unreachable";
|
|
@@ -133,16 +328,19 @@ export async function listProviderModels(
|
|
|
133
328
|
* probed. Runs in parallel.
|
|
134
329
|
*/
|
|
135
330
|
export async function discoverModels(
|
|
136
|
-
opts: DiscoveryOptions & { providers?: ProviderName[]; config?: Config } = {},
|
|
331
|
+
opts: DiscoveryOptions & { providers?: ProviderName[]; config?: Config; catalogFallback?: boolean } = {},
|
|
137
332
|
): Promise<ProviderModelsResult[]> {
|
|
138
333
|
const cfg = opts.config ?? (await readGlobalConfig());
|
|
139
334
|
const providers = opts.providers ?? [...PROVIDER_NAMES];
|
|
140
|
-
|
|
335
|
+
const useFallback = opts.catalogFallback !== false;
|
|
336
|
+
const results = await Promise.all(
|
|
141
337
|
providers.map(p =>
|
|
142
338
|
listProviderModels(p, {
|
|
143
339
|
...opts,
|
|
340
|
+
config: cfg,
|
|
144
341
|
baseUrl: p === "ollama" ? (cfg.ollamaBaseUrl ?? opts.baseUrl) : p === "openai" ? (cfg.openaiBaseUrl ?? opts.baseUrl) : opts.baseUrl,
|
|
145
342
|
}),
|
|
146
343
|
),
|
|
147
344
|
);
|
|
345
|
+
return useFallback ? results.map(catalogOr) : results;
|
|
148
346
|
}
|