jeo-code 0.8.1 → 0.8.3
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 +21 -0
- package/README.ja.md +10 -7
- package/README.ko.md +9 -6
- package/README.md +6 -5
- package/README.zh.md +10 -7
- package/package.json +1 -1
- package/src/agent/prompt-router.ts +56 -37
- package/src/ai/model-catalog-compat.ts +1 -1
- package/src/ai/model-catalog.ts +43 -38
- package/src/ai/model-discovery.ts +12 -0
- package/src/ai/model-manager.ts +36 -6
- package/src/ai/model-registry.ts +4 -0
- package/src/commands/launch.ts +16 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
The README mirrors the latest 5 entries — regenerate with `bun run changelog:sync`.
|
|
8
8
|
|
|
9
|
+
## [0.8.3] - 2026-07-08
|
|
10
|
+
_"잘못된 모델 설정하는 경우가 있는데" — the static Antigravity catalog had drifted badly from the LIVE Cloud Code Assist agent backend, and several current wire ids actively LIE about their tier: `gemini-3-flash-agent` is really "Gemini 3.5 Flash (High)" (the flagship agent model, which the name-suffix heuristic filed under the SMALL flash tier), `gemini-pro-agent` is really "Gemini 3.1 Pro (High)" (the code-agent model), and `gemini-3.1-pro-high` is DEPRECATED by the backend in favor of `gemini-pro-agent`. Probed the real `fetchAvailableModels` response (displayNames + agentModelSorts + deprecatedModelIds) and made it the single source of truth._
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **Antigravity catalog rebuilt from the live agent set** (`src/ai/model-catalog.ts`) — the 17 stale tuple rows (`antigravity/gpt-5.5`, `claude-opus-4-8*`, `gemini-3-pro-*`, `gemini-2.5-*`, deprecated `gemini-3.1-pro-high`, …) are replaced by the 8 rows the backend actually serves today, each pinned to its displayName truth with a new explicit `CatalogModel.sizeClass`: `gemini-3-flash-agent` ("Gemini 3.5 Flash (High)") = **large/high**, `gemini-3.5-flash-low` (Medium) = mid, `gemini-3.5-flash-extra-low` (Low) = small, `gemini-pro-agent` ("Gemini 3.1 Pro (High)", the **code-agent** model) = mid + RECOMMENDED badge (replaces `antigravity/gpt-5.5`), `gemini-3.1-pro-low` = **small/low**, plus selectable `claude-sonnet-4-6` (Thinking) = mid and `claude-opus-4-6-thinking` = large.
|
|
14
|
+
- **`sizeClassFor` honors the explicit `sizeClass`** (`src/agent/prompt-router.ts`) — the suffix heuristic misfiled every "agent" wire id (flash⇒small even when the model is the flagship; pro⇒mid even for the Low tier). Routing pools now place each Antigravity model in the tier its displayName declares: trivial→{3.1-pro-low, 3.5-flash-extra-low}, standard/high→{gemini-pro-agent, claude-sonnet-4-6, …}, complex→{gemini-3-flash-agent, claude-opus-4-6-thinking}. The now-meaningless `isAntigravityGeminiBelow31` version floor (wire ids no longer encode versions honestly) is deleted — the catalog rows themselves are the floor.
|
|
15
|
+
- **`cheapestCredentialed` prefers the smaller size class on a flat-price tie** — inside one subscription family every row shares the same family price (e.g. all Antigravity Gemini rows), so the old newest-first tiebreak picked the FLAGSHIP (`gemini-3-flash-agent`) as "cheapest". Trivial-tier auto-select now lands on the actual Low tier (`gemini-3.5-flash-extra-low`).
|
|
16
|
+
- **Deprecated wire id keeps working** (`src/ai/model-registry.ts`) — `antigravity/gemini-3.1-pro-high` → `antigravity/gemini-pro-agent` added to `BUILTIN_ALIASES`, mirroring the backend's own `deprecatedModelIds.newModelId` mapping, so existing configs/roles pinned to the old id resolve to the successor instead of erroring. `model-manager.ts`'s stale duplicate `ALIAS_DEFAULTS` table is deleted in favor of the single `BUILTIN_ALIASES` source (the call path now also expands the `kimi` alias, previously missing there).
|
|
17
|
+
|
|
18
|
+
## [0.8.2] - 2026-07-08
|
|
19
|
+
_"프롬프트라우팅이 api 입력을 요구하면서 동작안해" — prompt routing kept demanding an API key even when the user was already logged in via OAuth (GPT showed the same symptom). Root cause: every routing gate was PROVIDER-level ("any stored credential exists") while OAuth credentials are MODEL-level (ChatGPT OAuth serves only Codex ids, Kimi OAuth only the Kimi Code catalog, gemini OAuth nothing at all since the Cloud Code Assist masquerade was removed) — so routing happily picked models the stored login could never serve, and the turn then failed at call time asking for `<PROVIDER>_API_KEY`. Also restored a missing export that had broken `bun run typecheck` on HEAD._
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- **Model-level credential gate for prompt routing** (`src/ai/model-manager.ts`, `src/agent/prompt-router.ts`) — new shared `modelServableWithConfig(provider, model, config)` predicate mirrors `resolveCall`'s real serving rules: an API key serves the provider's full catalog; OAuth serves exactly what `oauthServesModel` verifies end-to-end (openai→`gpt-5.5`/`gpt-5.4` only, kimi→Kimi Code ids only, gemini→none without `GEMINI_API_KEY`); antigravity is OAuth-only (an `providers.antigravity` API key alone cannot serve calls — previously counted as credentialed); a configured `openaiBaseUrl` stays the keyless local-proxy path. `isAutoSelectCandidate` now applies this predicate, so auto-routing only ever selects models the stored OAuth **or** API-key login can actually serve — both auth modes enable models, each scoped to what it really serves.
|
|
23
|
+
- **Routing veto gate closes the explicit-pin blind spot** (`src/commands/launch.ts`) — the per-turn gate previously checked only provider-level readiness (`describeProvider().ready`), so an explicitly configured `routing.tiers.*.model`/`roles.*` pointing at a model the stored OAuth cannot serve (e.g. `gpt-4o` with a ChatGPT-OAuth-only login) passed the gate and then failed mid-turn demanding an API key. The gate now also verifies `modelServableWithConfig` and falls back to the session/default model with an actionable one-time notice ("set `<PROVIDER>_API_KEY` or reconfigure routing.tiers/roles"), keeping routing strictly fail-open.
|
|
24
|
+
- **Gemini models are exposed ONLY under API-key auth** (`src/ai/model-discovery.ts`) — a gemini OAuth token can still LIST models from `generativelanguage.googleapis.com` but can no longer SERVE them, so `/model`/`/provider` pickers offered models every call would reject. `listProviderModels("gemini")` now short-circuits an OAuth-only credential with an actionable error (set `GEMINI_API_KEY`, or use `antigravity/*`) before any fetch, and `catalogOr` no longer resurrects the static gemini catalog for oauth-sourced failures. With `GEMINI_API_KEY` set (alone or alongside OAuth — the existing API-key-preference swap), live discovery is unchanged.
|
|
25
|
+
- **Restored the missing `compareReleaseDate` export** (`src/ai/model-catalog.ts`) — HEAD (`bb1b6c0`) imported it in `prompt-router.ts` but the helper itself never landed (another shared-worktree partial-stage, same failure mode as the v0.8.0 postmortem), so `bun run typecheck` failed on a pristine checkout. Reimplemented per its documented contract: lexicographic `"YYYY-MM"` compare, missing date sorts oldest.
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
- **`/route` documented in the README slash-command table** — `/route [status|on|off|why]`: session-local routing toggle + last-decision explainer, alongside the existing `/model`/`/thinking` rows.
|
|
29
|
+
|
|
9
30
|
## [0.8.1] - 2026-07-08
|
|
10
31
|
_Fix-forward for a functional regression this same session shipped in v0.8.0. This repo's working tree is shared with a concurrent interactive session; `git add`-ing `src/ai/provider-status.ts` for v0.8.0's routing-veto-gate fix also swept up that session's already-modified, uncommitted copy of the SAME file (containing an unrelated design change: gemini OAuth alone no longer serving `google/gemini-*` models) without its companion piece in `src/auth/flows/index.ts` — so v0.8.0 shipped with a test suite (`provider-status.test.ts`) asserting behavior whose supporting data flag never landed. CI caught this immediately post-release: 2 failing tests on the exact tagged commit. Investigated, confirmed the missing piece was real (and independently observable live via `jeo doctor` before this session had made ANY of its own edits — not a stray/speculative change), and completed it._
|
|
11
32
|
|
package/README.ja.md
CHANGED
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
|
|
39
39
|
## ハイライト
|
|
40
40
|
|
|
41
|
-
- **マルチプロバイダ・単一ループ** — Anthropic / OpenAI(+Codex) / Gemini / Antigravity / Ollama / LM Studio に加え、OpenAI・Anthropic 互換クラウド20以上(Groq、DeepSeek、Mistral、OpenRouter、xAI、Kimi、z.ai など)を均一な JSON ツールループで。入力欄から OAuth ログイン(`/provider login`)
|
|
41
|
+
- **マルチプロバイダ・単一ループ** — Anthropic / OpenAI(+Codex) / Gemini / Antigravity / Ollama / LM Studio に加え、OpenAI・Anthropic 互換クラウド20以上(Groq、DeepSeek、Mistral、OpenRouter、xAI、Kimi、z.ai など)を均一な JSON ツールループで。入力欄から OAuth ログイン(`/provider login`)、モデル選択は即座にデフォルトとして永続化。プロンプトルーティングは実際に使える認証済み経路だけを自動選択します: Gemini OAuth は provider-qualified な `antigravity/*` エージェントセット(Gemini 3.5 Flash 各ティア、Gemini 3.1 Pro、Claude Sonnet/Opus 4.6)へ向かい、`GEMINI_API_KEY` が必要な public `google/gemini-*` 行は選びません。
|
|
42
42
|
- **編集の完全性** — read 出力にコンテンツアンカー(`42ab|`)が付き、アンカー付き編集は現在のファイルと照合・行移動時は自動再マッピング・不一致時は最新内容と共に拒否されます。
|
|
43
43
|
- **自己修正の検証ループ** — post-edit フック(tsc / eslint / テスト)を設定すると、エージェントが診断を*自ら読み*ループ内で修正します。フックが赤のままだと `done` はブロックされます。
|
|
44
44
|
- **芝居なしの本物のゲート** — `ralplan` の合議はリポジトリを実際に読む critic サブエージェントで、`[OKAY]` 評決が永続化され `jeo approve` がそれを*要求*します。`ultragoal` は誠実に報告します(スイート1回実行はグローバル信号であり、基準ごとの合格を捏造しません)。
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
- **動的ステップ予算** — 直近のツール呼び出しが新規の進捗を示す間は延長され、停滞すれば要約に収束。サブエージェントは厳密なステップ契約を維持。
|
|
47
47
|
- **インライン TUI** — 完了した作業は実スクロールバックに流れ(ターン中も tmux ホイール可)、エージェント実行中も通常のクエリ入力欄が表示されたまま編集できます。Ctrl+O の詳細トグル、テーマ、クリップボード画像貼り付け(Ctrl+V)、CJK/絵文字対応の幅計算。
|
|
48
48
|
- **ブラウザツール** — Playwright によるヘッドレス Chromium 自動化を第一級のエージェントツールとして搭載: 名前付きタブを再利用しつつ `open`/`close`/`run`/`act`、スクリーンショットより `observe` でタグ付けした要素 id を優先。`npx playwright install chromium` を一度実行する必要があります(バンドルされていません — jeo 自体はネイティブ依存ゼロのまま、ブラウザバイナリは Playwright 側の別ダウンロードです)。
|
|
49
|
-
- **リモートサブエージェント可視化(Telegram)** — ボットを一度ペアリング(`jeo notify setup`)すれば、`jeo daemon start` がサブエージェントの状態遷移(開始 → 完了/失敗/キャンセル)ごとにメッセージを送り、`/subagents`、`/steer <id> <subagentId> <msg>`、`/cancel <id> <subagentId>`
|
|
49
|
+
- **リモートサブエージェント可視化(Telegram)** — ボットを一度ペアリング(`jeo notify setup`)すれば、`jeo daemon start` がサブエージェントの状態遷移(開始 → 完了/失敗/キャンセル)ごとにメッセージを送り、`/subagents`、`/steer <id> <subagentId> <msg>`、`/cancel <id> <subagentId>` を受け付けます。Telegram フォーラムトピック、インラインキーボード、画像添付を含む `gjc` 完全パリティを提供し、コマンドはペアリングされたチャットのみ許可されます。
|
|
50
50
|
|
|
51
51
|
## インストール
|
|
52
52
|
|
|
@@ -79,11 +79,12 @@ jeo --tmux # 独立した tmux セッションで実行
|
|
|
79
79
|
| `/provider login <name>` · `/logout` | 入力欄から OAuth ログイン/ログアウト |
|
|
80
80
|
| `/agents [role]` · `/subagent` | ロール別(executor/planner/architect/critic)モデル・thinking・ステップ設定 |
|
|
81
81
|
| `/thinking [level]` | デフォルト推論予算(low…xhigh)の表示/設定 |
|
|
82
|
-
| `/
|
|
82
|
+
| `/route [status\|on\|off\|why]` | セッション単位のプロンプトベース・モデルルーティングの切替 · 直近のルーティング判断の説明(設定済み資格情報 — OAuth または API キー — が実際に提供するモデルの中でのみ自動ルーティング) |
|
|
83
|
+
| `/fast [on\|off\|status]` | 現在のモデルが low 推論をサポートする場合に fast thinking モードを切替 |
|
|
83
84
|
| `/skill` · `$<skill> [intent]` | ワークフロースキルの一覧/実行(`$team "task"` 形式) |
|
|
84
85
|
| `/view` · `/diff` · `/find` · `/search` | コード表示、git diff、ファイル/パターン検索 |
|
|
85
86
|
| `/new` · `/resume` · `/sessions` | セッション管理 |
|
|
86
|
-
| `/history [n
|
|
87
|
+
| `/history [n\|all]` · `/export` | 作業アクティビティ履歴を読みやすくスクロールバックへ再出力・トランスクリプト出力 |
|
|
87
88
|
| `/retry` · `/btw <q>` | 直前要求の再試行 · 履歴に残らないサイド質問 |
|
|
88
89
|
| `/usage` · `/context` · `/compact` | トークン使用量、コンテキスト内訳、手動コンパクション |
|
|
89
90
|
| `/theme` · `/config` · `/help` | テーマ、ランタイム設定、ヘルプ |
|
|
@@ -212,8 +213,10 @@ JEO_TUI_THEME=cosmic # cosmic/matrix/solar/red-claw/blue-crab/mono/au
|
|
|
212
213
|
JEO_TUI_ALT_SCREEN=1 # レガシー alt-screen ターン(デフォルト: インラインスクロールバック)
|
|
213
214
|
JEO_STEP_BASE=24 # 動的ステップ予算のローリングベース
|
|
214
215
|
JEO_STEP_HARD_CAP=600 # 絶対終了保証
|
|
215
|
-
JEO_STREAM_MAX_MS=
|
|
216
|
+
JEO_STREAM_MAX_MS=1800000 # 全体ストリーム期限(デフォルト30分; スロードリップストリームを制限し、能動中のストリームそのものを止めるための値ではありません); 0 で無効化
|
|
216
217
|
JEO_STREAM_IDLE_MS=300000 # チャンク単位のアイドル上限(デフォルト300秒); 最初のトークン前が長い低速/ローカルバックエンドでは引き上げてください
|
|
218
|
+
JEO_CALL_TIMEOUT_MS=1800000 # 非ストリーミング呼び出しの壁時計上限(デフォルト30分; compaction/subagents/goal-verify)
|
|
219
|
+
JEO_TURN_MAX_MS=1800000 # ターン停滞予算: ツール進捗がない最大時間(デフォルト30分); 0 で無効化
|
|
217
220
|
JEO_TOOL_OUTPUT_MAX=4000 # モデル可視のツール出力上限(全文はアーティファクトへ)
|
|
218
221
|
```
|
|
219
222
|
|
|
@@ -259,11 +262,11 @@ CI は `.github/workflows/npm-publish.yml` で公開します — GitHub リリ
|
|
|
259
262
|
## 変更履歴 (Changelog)
|
|
260
263
|
|
|
261
264
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
265
|
+
- **[0.8.3]** (2026-07-08) — "잘못된 모델 설정하는 경우가 있는데" — the static Antigravity catalog had drifted badly from the LIVE Cloud Code Assist agent backend, and several current wire ids actively LIE about their tier: `gemini-3-flash-agent` is really "Gemini 3.5 Flash (High)" (the flagship agent model, which the name-suffix heuristic filed under the SMALL flash tier), `gemini-pro-agent` is really "Gemini 3.1 Pro (High)" (the code-agent model), and `gemini-3.1-pro-high` is DEPRECATED by the backend in favor of `gemini-pro-agent`. Probed the real `fetchAvailableModels` response (displayNames + agentModelSorts + deprecatedModelIds) and made it the single source of truth.
|
|
266
|
+
- **[0.8.2]** (2026-07-08) — "프롬프트라우팅이 api 입력을 요구하면서 동작안해" — prompt routing kept demanding an API key even when the user was already logged in via OAuth (GPT showed the same symptom). Root cause: every routing gate was PROVIDER-level ("any stored credential exists") while OAuth credentials are MODEL-level (ChatGPT OAuth serves only Codex ids, Kimi OAuth only the Kimi Code catalog, gemini OAuth nothing at all since the Cloud Code Assist masquerade was removed) — so routing happily picked models the stored login could never serve, and the turn then failed at call time asking for `<PROVIDER>_API_KEY`. Also restored a missing export that had broken `bun run typecheck` on HEAD.
|
|
262
267
|
- **[0.8.1]** (2026-07-08) — Fix-forward for a functional regression this same session shipped in v0.8.0. This repo's working tree is shared with a concurrent interactive session; `git add`-ing `src/ai/provider-status.ts` for v0.8.0's routing-veto-gate fix also swept up that session's already-modified, uncommitted copy of the SAME file (containing an unrelated design change: gemini OAuth alone no longer serving `google/gemini-*` models) without its companion piece in `src/auth/flows/index.ts` — so v0.8.0 shipped with a test suite (`provider-status.test.ts`) asserting behavior whose supporting data flag never landed. CI caught this immediately post-release: 2 failing tests on the exact tagged commit. Investigated, confirmed the missing piece was real (and independently observable live via `jeo doctor` before this session had made ANY of its own edits — not a stray/speculative change), and completed it.
|
|
263
268
|
- **[0.8.0]** (2026-07-08) — Root-caused a genuine reliability bug in the routing veto gate ("라우트가 동작후 api 키인증오류가나오는데 근본적인 원인 개선해"), then regenerated the animated README mascot to visualize the smart cross-provider routing this release cycle has been building: prompt routing that finds the cheapest usable path, with a "money saver" visual motif.
|
|
264
269
|
- **[0.7.58]** (2026-07-07) — Applied the previous entry's `routing.crossProviderPool` to the real live session config, then deep-researched every catalogued model's actual public release date (2026-07 sourced, per-provider parallel research with citations) to answer "모델 설정은 2026년도 기준으로 딥서치해서 모델리스트 최신순으로 정렬" — while researching, found and fixed a genuine ranking bug this surfaced: `strongestCredentialed`'s tiebreak on a full capability tie (identical thinking/output/context) fell through to a raw alphabetical canonical-id compare, which silently picked the OLDEST model in a same-spec family (`claude-opus-4-6` over `claude-opus-4-7`/`4-8`, purely because `"4-6" < "4-8"` as a string) — confirmed reproducible before the fix, confirmed corrected after.
|
|
265
|
-
- **[0.7.57]** (2026-07-07) — "동급모델간 크로스 프로바이더 라우팅이 동작하게 개선" — v0.7.56's tier auto-select was single-winner by design (always the one cheapest/strongest model), so a user with `roles.smol` pinned (or simply multiple credentialed providers) never saw actual distribution across equivalent-class models. Deep-research-informed redesign: production LLM routers (LiteLLM, Bifrost/VoidLLM) model this as a named "model group" spanning provider deployments with a distribution strategy, not a single deterministic pick — jeo now supports both, opt-in.
|
|
266
|
-
- **[0.7.56]** (2026-07-07) — "서로다른 프로바이더로 급에 따라 나눠서 설정하도록 개선" — PromptRouter's `trivial`/`complex` tiers previously collapsed to `defaultModel` whenever `roles.smol`/`roles.slow`/`routing.tiers.*` were left unconfigured (the documented "safe no-op absent configuration" contract), which meant a user with multiple providers credentialed never actually got cross-provider routing without hand-picking a model per tier. Also the long-term ask: routing must stay correct as jeo's model catalog evolves, never a hand-maintained tier→model table that goes stale.
|
|
267
270
|
|
|
268
271
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
269
272
|
<!-- CHANGELOG:END -->
|
package/README.ko.md
CHANGED
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
|
|
39
39
|
## 하이라이트
|
|
40
40
|
|
|
41
|
-
- **멀티 프로바이더, 단일 루프** — Anthropic / OpenAI(+Codex) / Gemini / Antigravity / Ollama / LM Studio, 그리고 OpenAI·Anthropic 호환 클라우드 20종 이상(Groq, DeepSeek, Mistral, OpenRouter, xAI, Kimi, z.ai 등)까지 균일한 JSON 도구 루프로. 입력창에서 바로 OAuth 로그인(`/provider login`), 모델 선택은 즉시 기본값으로
|
|
41
|
+
- **멀티 프로바이더, 단일 루프** — Anthropic / OpenAI(+Codex) / Gemini / Antigravity / Ollama / LM Studio, 그리고 OpenAI·Anthropic 호환 클라우드 20종 이상(Groq, DeepSeek, Mistral, OpenRouter, xAI, Kimi, z.ai 등)까지 균일한 JSON 도구 루프로. 입력창에서 바로 OAuth 로그인(`/provider login`), 모델 선택은 즉시 기본값으로 영속됩니다. 프롬프트 라우팅은 실제 사용 가능한 인증 경로만 자동 선택합니다: Gemini OAuth는 provider-qualified `antigravity/*` 에이전트 세트(Gemini 3.5 Flash 등급, Gemini 3.1 Pro, Claude Sonnet/Opus 4.6)로만 가고, `GEMINI_API_KEY`가 필요한 public `google/gemini-*` 행은 고르지 않습니다.
|
|
42
42
|
- **편집 무결성** — read 출력에 콘텐츠 앵커(`42ab|`)가 붙고, 앵커 편집은 현재 파일과 대조 검증·줄 이동 시 자동 재매핑·불일치 시 최신 내용과 함께 거부 — 파일을 오염시키지 않습니다.
|
|
43
43
|
- **자기수정 검증 루프** — post-edit 훅(tsc / eslint / 테스트)을 설정하면 에이전트가 진단을 *직접 읽고* 루프 안에서 수정합니다. 훅이 빨간 상태면 `done`이 차단됩니다.
|
|
44
44
|
- **연극 없는 진짜 게이트** — `ralplan` 합의는 실제 저장소를 읽는 critic 서브에이전트이며 `[OKAY]` 평결이 영속되고 `jeo approve`가 이를 *요구*합니다. `ultragoal`은 정직하게 보고합니다(스위트 1회 실행은 전역 신호일 뿐, 기준별 통과를 조작하지 않음).
|
|
@@ -80,11 +80,12 @@ jeo --tmux # 독립 tmux 세션에서 실행
|
|
|
80
80
|
| `/provider login <name>` · `/logout` | 입력창에서 OAuth 로그인/로그아웃 |
|
|
81
81
|
| `/agents [role]` · `/subagent` | 역할별(executor/planner/architect/critic) 모델·thinking·스텝 구성 |
|
|
82
82
|
| `/thinking [level]` | 기본 추론 예산(low…xhigh) 조회/설정 |
|
|
83
|
-
| `/
|
|
83
|
+
| `/route [status\|on\|off\|why]` | 세션별 프롬프트 기반 모델 라우팅 켜기/끄기 · 마지막 라우팅 결정 설명(설정된 자격증명 — OAuth 또는 API 키 — 이 실제로 서비스하는 모델 안에서만 자동 라우팅) |
|
|
84
|
+
| `/fast [on\|off\|status]` | 현재 모델이 low 추론을 지원하면 fast thinking 모드를 켜고 끔 |
|
|
84
85
|
| `/skill` · `$<skill> [intent]` | 워크플로 스킬 목록/실행(`$team "작업"` 스타일) |
|
|
85
86
|
| `/view` · `/diff` · `/find` · `/search` | 코드 보기, git diff, 파일/패턴 검색 |
|
|
86
87
|
| `/new` · `/resume` · `/sessions` | 세션 관리 |
|
|
87
|
-
| `/history [n
|
|
88
|
+
| `/history [n\|all]` · `/export` | 작업 활동 히스토리를 읽기 좋게 스크롤백에 재출력 · 트랜스크립트 내보내기 |
|
|
88
89
|
| `/retry` · `/btw <질문>` | 마지막 요청 재시도 · 히스토리에 안 남는 사이드 질문 |
|
|
89
90
|
| `/usage` · `/context` · `/compact` | 토큰 사용량, 컨텍스트 내역, 수동 컴팩션 |
|
|
90
91
|
| `/theme` · `/config` · `/help` | 테마, 런타임 설정, 도움말 |
|
|
@@ -213,8 +214,10 @@ JEO_TUI_THEME=cosmic # cosmic/matrix/solar/red-claw/blue-crab/mono/au
|
|
|
213
214
|
JEO_TUI_ALT_SCREEN=1 # 레거시 alt-screen 턴(기본: 인라인 스크롤백)
|
|
214
215
|
JEO_STEP_BASE=24 # 동적 스텝 버짓의 롤링 베이스
|
|
215
216
|
JEO_STEP_HARD_CAP=600 # 절대 종료 보증
|
|
216
|
-
JEO_STREAM_MAX_MS=
|
|
217
|
+
JEO_STREAM_MAX_MS=1800000 # 전체 스트림 데드라인(기본 30분; 슬로우드립 스트림 제한, 활성 스트림 자체를 끊기 위한 값은 아님); 0이면 비활성화
|
|
217
218
|
JEO_STREAM_IDLE_MS=300000 # 청크당 유휴 타임아웃(기본 300초); 첫 토큰 전 침묵이 긴 느린/로컬 백엔드는 값을 높이세요
|
|
219
|
+
JEO_CALL_TIMEOUT_MS=1800000 # 비스트리밍 호출 벽시계 제한(기본 30분; 컴팩션/서브에이전트/goal-verify)
|
|
220
|
+
JEO_TURN_MAX_MS=1800000 # 턴 정체 예산: 도구 진전이 없는 최대 시간(기본 30분); 0이면 비활성화
|
|
218
221
|
JEO_TOOL_OUTPUT_MAX=4000 # 모델 가시 도구 출력 캡(전체는 아티팩트로 스필)
|
|
219
222
|
```
|
|
220
223
|
|
|
@@ -260,11 +263,11 @@ CI는 `.github/workflows/npm-publish.yml`로 배포합니다 — GitHub 릴리
|
|
|
260
263
|
## 변경 이력 (Changelog)
|
|
261
264
|
|
|
262
265
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
266
|
+
- **[0.8.3]** (2026-07-08) — "잘못된 모델 설정하는 경우가 있는데" — the static Antigravity catalog had drifted badly from the LIVE Cloud Code Assist agent backend, and several current wire ids actively LIE about their tier: `gemini-3-flash-agent` is really "Gemini 3.5 Flash (High)" (the flagship agent model, which the name-suffix heuristic filed under the SMALL flash tier), `gemini-pro-agent` is really "Gemini 3.1 Pro (High)" (the code-agent model), and `gemini-3.1-pro-high` is DEPRECATED by the backend in favor of `gemini-pro-agent`. Probed the real `fetchAvailableModels` response (displayNames + agentModelSorts + deprecatedModelIds) and made it the single source of truth.
|
|
267
|
+
- **[0.8.2]** (2026-07-08) — "프롬프트라우팅이 api 입력을 요구하면서 동작안해" — prompt routing kept demanding an API key even when the user was already logged in via OAuth (GPT showed the same symptom). Root cause: every routing gate was PROVIDER-level ("any stored credential exists") while OAuth credentials are MODEL-level (ChatGPT OAuth serves only Codex ids, Kimi OAuth only the Kimi Code catalog, gemini OAuth nothing at all since the Cloud Code Assist masquerade was removed) — so routing happily picked models the stored login could never serve, and the turn then failed at call time asking for `<PROVIDER>_API_KEY`. Also restored a missing export that had broken `bun run typecheck` on HEAD.
|
|
263
268
|
- **[0.8.1]** (2026-07-08) — Fix-forward for a functional regression this same session shipped in v0.8.0. This repo's working tree is shared with a concurrent interactive session; `git add`-ing `src/ai/provider-status.ts` for v0.8.0's routing-veto-gate fix also swept up that session's already-modified, uncommitted copy of the SAME file (containing an unrelated design change: gemini OAuth alone no longer serving `google/gemini-*` models) without its companion piece in `src/auth/flows/index.ts` — so v0.8.0 shipped with a test suite (`provider-status.test.ts`) asserting behavior whose supporting data flag never landed. CI caught this immediately post-release: 2 failing tests on the exact tagged commit. Investigated, confirmed the missing piece was real (and independently observable live via `jeo doctor` before this session had made ANY of its own edits — not a stray/speculative change), and completed it.
|
|
264
269
|
- **[0.8.0]** (2026-07-08) — Root-caused a genuine reliability bug in the routing veto gate ("라우트가 동작후 api 키인증오류가나오는데 근본적인 원인 개선해"), then regenerated the animated README mascot to visualize the smart cross-provider routing this release cycle has been building: prompt routing that finds the cheapest usable path, with a "money saver" visual motif.
|
|
265
270
|
- **[0.7.58]** (2026-07-07) — Applied the previous entry's `routing.crossProviderPool` to the real live session config, then deep-researched every catalogued model's actual public release date (2026-07 sourced, per-provider parallel research with citations) to answer "모델 설정은 2026년도 기준으로 딥서치해서 모델리스트 최신순으로 정렬" — while researching, found and fixed a genuine ranking bug this surfaced: `strongestCredentialed`'s tiebreak on a full capability tie (identical thinking/output/context) fell through to a raw alphabetical canonical-id compare, which silently picked the OLDEST model in a same-spec family (`claude-opus-4-6` over `claude-opus-4-7`/`4-8`, purely because `"4-6" < "4-8"` as a string) — confirmed reproducible before the fix, confirmed corrected after.
|
|
266
|
-
- **[0.7.57]** (2026-07-07) — "동급모델간 크로스 프로바이더 라우팅이 동작하게 개선" — v0.7.56's tier auto-select was single-winner by design (always the one cheapest/strongest model), so a user with `roles.smol` pinned (or simply multiple credentialed providers) never saw actual distribution across equivalent-class models. Deep-research-informed redesign: production LLM routers (LiteLLM, Bifrost/VoidLLM) model this as a named "model group" spanning provider deployments with a distribution strategy, not a single deterministic pick — jeo now supports both, opt-in.
|
|
267
|
-
- **[0.7.56]** (2026-07-07) — "서로다른 프로바이더로 급에 따라 나눠서 설정하도록 개선" — PromptRouter's `trivial`/`complex` tiers previously collapsed to `defaultModel` whenever `roles.smol`/`roles.slow`/`routing.tiers.*` were left unconfigured (the documented "safe no-op absent configuration" contract), which meant a user with multiple providers credentialed never actually got cross-provider routing without hand-picking a model per tier. Also the long-term ask: routing must stay correct as jeo's model catalog evolves, never a hand-maintained tier→model table that goes stale.
|
|
268
271
|
|
|
269
272
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
270
273
|
<!-- CHANGELOG:END -->
|
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ Run `jeo` inside a repository and it reads files, edits them, runs commands, and
|
|
|
38
38
|
|
|
39
39
|
## Highlights
|
|
40
40
|
|
|
41
|
-
- **Multi-provider, one loop** — Anthropic / OpenAI (+Codex) / Gemini / Antigravity / Ollama / LM Studio, plus 20+ OpenAI- and Anthropic-compatible clouds (Groq, DeepSeek, Mistral, OpenRouter, xAI, Kimi, z.ai, …), all behind one uniform JSON tool loop. OAuth login from the input box (`/provider login`), every model pick persists as the new default.
|
|
41
|
+
- **Multi-provider, one loop** — Anthropic / OpenAI (+Codex) / Gemini / Antigravity / Ollama / LM Studio, plus 20+ OpenAI- and Anthropic-compatible clouds (Groq, DeepSeek, Mistral, OpenRouter, xAI, Kimi, z.ai, …), all behind one uniform JSON tool loop. OAuth login happens from the input box (`/provider login`), every model pick persists as the new default, and prompt routing only auto-selects usable credentialed paths: Gemini OAuth goes through the provider-qualified `antigravity/*` agent set (Gemini 3.5 Flash tiers, Gemini 3.1 Pro, Claude Sonnet/Opus 4.6), never public `google/gemini-*` rows that require `GEMINI_API_KEY`.
|
|
42
42
|
- **Edit integrity** — read output carries content anchors (`42ab|`); anchored edits are verified against the current file, re-mapped when lines shifted, and rejected with fresh content instead of corrupting.
|
|
43
43
|
- **Self-correcting verification loop** — configure a post-edit hook (tsc / eslint / tests) and the agent *sees* the diagnostics and fixes them in-loop; a red hook blocks `done` until resolved.
|
|
44
44
|
- **Real gates, no theater** — `ralplan` consensus is a repo-grounded critic subagent whose `[OKAY]` verdict is persisted and *required* by `jeo approve`; `ultragoal` reports honestly (a suite run is a global signal, never fabricated per-criterion passes).
|
|
@@ -80,11 +80,12 @@ Inside the `jeo` REPL (Tab autocompletes; `/` opens the palette).
|
|
|
80
80
|
| `/provider login <name>` · `/logout` | OAuth login/logout from the input box |
|
|
81
81
|
| `/agents [role]` · `/subagent` | Per-role (executor/planner/architect/critic) model · thinking · step config |
|
|
82
82
|
| `/thinking [level]` | Show/set default reasoning budget (low…xhigh) |
|
|
83
|
-
| `/
|
|
83
|
+
| `/route [status\|on\|off\|why]` | Toggle prompt-based model routing for this session · explain the last routing decision (auto-routes each turn to a tier-appropriate model among the models your configured credentials — OAuth or API key — actually serve) |
|
|
84
|
+
| `/fast [on\|off\|status]` | Toggle fast thinking mode when the active model advertises low reasoning |
|
|
84
85
|
| `/skill` · `$<skill> [intent]` | List/run workflow skills (`$team "task"` style) |
|
|
85
86
|
| `/view` · `/diff` · `/find` · `/search` | Code view, git diff, file/pattern search |
|
|
86
87
|
| `/new` · `/resume` · `/sessions` | Session management |
|
|
87
|
-
| `/history [n
|
|
88
|
+
| `/history [n\|all]` · `/export` | Reprint readable worked activity history into scrollback · transcript export |
|
|
88
89
|
| `/retry` · `/btw <q>` | Retry last request · side question without touching history |
|
|
89
90
|
| `/usage` · `/context` · `/compact` | Token usage, context breakdown, manual compaction |
|
|
90
91
|
| `/theme` · `/config` · `/help` | Theme, runtime config, help |
|
|
@@ -264,11 +265,11 @@ Huge thanks to [gajae-code](https://github.com/Yeachan-Heo/gajae-code) for the i
|
|
|
264
265
|
## Changelog
|
|
265
266
|
|
|
266
267
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
268
|
+
- **[0.8.3]** (2026-07-08) — "잘못된 모델 설정하는 경우가 있는데" — the static Antigravity catalog had drifted badly from the LIVE Cloud Code Assist agent backend, and several current wire ids actively LIE about their tier: `gemini-3-flash-agent` is really "Gemini 3.5 Flash (High)" (the flagship agent model, which the name-suffix heuristic filed under the SMALL flash tier), `gemini-pro-agent` is really "Gemini 3.1 Pro (High)" (the code-agent model), and `gemini-3.1-pro-high` is DEPRECATED by the backend in favor of `gemini-pro-agent`. Probed the real `fetchAvailableModels` response (displayNames + agentModelSorts + deprecatedModelIds) and made it the single source of truth.
|
|
269
|
+
- **[0.8.2]** (2026-07-08) — "프롬프트라우팅이 api 입력을 요구하면서 동작안해" — prompt routing kept demanding an API key even when the user was already logged in via OAuth (GPT showed the same symptom). Root cause: every routing gate was PROVIDER-level ("any stored credential exists") while OAuth credentials are MODEL-level (ChatGPT OAuth serves only Codex ids, Kimi OAuth only the Kimi Code catalog, gemini OAuth nothing at all since the Cloud Code Assist masquerade was removed) — so routing happily picked models the stored login could never serve, and the turn then failed at call time asking for `<PROVIDER>_API_KEY`. Also restored a missing export that had broken `bun run typecheck` on HEAD.
|
|
267
270
|
- **[0.8.1]** (2026-07-08) — Fix-forward for a functional regression this same session shipped in v0.8.0. This repo's working tree is shared with a concurrent interactive session; `git add`-ing `src/ai/provider-status.ts` for v0.8.0's routing-veto-gate fix also swept up that session's already-modified, uncommitted copy of the SAME file (containing an unrelated design change: gemini OAuth alone no longer serving `google/gemini-*` models) without its companion piece in `src/auth/flows/index.ts` — so v0.8.0 shipped with a test suite (`provider-status.test.ts`) asserting behavior whose supporting data flag never landed. CI caught this immediately post-release: 2 failing tests on the exact tagged commit. Investigated, confirmed the missing piece was real (and independently observable live via `jeo doctor` before this session had made ANY of its own edits — not a stray/speculative change), and completed it.
|
|
268
271
|
- **[0.8.0]** (2026-07-08) — Root-caused a genuine reliability bug in the routing veto gate ("라우트가 동작후 api 키인증오류가나오는데 근본적인 원인 개선해"), then regenerated the animated README mascot to visualize the smart cross-provider routing this release cycle has been building: prompt routing that finds the cheapest usable path, with a "money saver" visual motif.
|
|
269
272
|
- **[0.7.58]** (2026-07-07) — Applied the previous entry's `routing.crossProviderPool` to the real live session config, then deep-researched every catalogued model's actual public release date (2026-07 sourced, per-provider parallel research with citations) to answer "모델 설정은 2026년도 기준으로 딥서치해서 모델리스트 최신순으로 정렬" — while researching, found and fixed a genuine ranking bug this surfaced: `strongestCredentialed`'s tiebreak on a full capability tie (identical thinking/output/context) fell through to a raw alphabetical canonical-id compare, which silently picked the OLDEST model in a same-spec family (`claude-opus-4-6` over `claude-opus-4-7`/`4-8`, purely because `"4-6" < "4-8"` as a string) — confirmed reproducible before the fix, confirmed corrected after.
|
|
270
|
-
- **[0.7.57]** (2026-07-07) — "동급모델간 크로스 프로바이더 라우팅이 동작하게 개선" — v0.7.56's tier auto-select was single-winner by design (always the one cheapest/strongest model), so a user with `roles.smol` pinned (or simply multiple credentialed providers) never saw actual distribution across equivalent-class models. Deep-research-informed redesign: production LLM routers (LiteLLM, Bifrost/VoidLLM) model this as a named "model group" spanning provider deployments with a distribution strategy, not a single deterministic pick — jeo now supports both, opt-in.
|
|
271
|
-
- **[0.7.56]** (2026-07-07) — "서로다른 프로바이더로 급에 따라 나눠서 설정하도록 개선" — PromptRouter's `trivial`/`complex` tiers previously collapsed to `defaultModel` whenever `roles.smol`/`roles.slow`/`routing.tiers.*` were left unconfigured (the documented "safe no-op absent configuration" contract), which meant a user with multiple providers credentialed never actually got cross-provider routing without hand-picking a model per tier. Also the long-term ask: routing must stay correct as jeo's model catalog evolves, never a hand-maintained tier→model table that goes stale.
|
|
272
273
|
|
|
273
274
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
274
275
|
<!-- CHANGELOG:END -->
|
package/README.zh.md
CHANGED
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
|
|
39
39
|
## 亮点
|
|
40
40
|
|
|
41
|
-
- **多提供商、单一循环** — Anthropic / OpenAI(+Codex) / Gemini / Antigravity / Ollama / LM Studio,以及 20+ 个 OpenAI、Anthropic 兼容云服务(Groq、DeepSeek、Mistral、OpenRouter、xAI、Kimi、z.ai 等),统一在一个 JSON 工具循环中。输入框内直接 OAuth 登录(`/provider login`)
|
|
41
|
+
- **多提供商、单一循环** — Anthropic / OpenAI(+Codex) / Gemini / Antigravity / Ollama / LM Studio,以及 20+ 个 OpenAI、Anthropic 兼容云服务(Groq、DeepSeek、Mistral、OpenRouter、xAI、Kimi、z.ai 等),统一在一个 JSON 工具循环中。输入框内直接 OAuth 登录(`/provider login`),模型选择即刻持久化为默认值。Prompt routing 只会自动选择真实可用的凭据路径: Gemini OAuth 会走 provider-qualified 的 `antigravity/*` 代理模型集(Gemini 3.5 Flash 各档、Gemini 3.1 Pro、Claude Sonnet/Opus 4.6),绝不选择需要 `GEMINI_API_KEY` 的 public `google/gemini-*` 行。
|
|
42
42
|
- **编辑完整性** — read 输出携带内容锚点(`42ab|`);带锚点的编辑会与当前文件校验、行移动时自动重映射、不匹配时连同最新内容一起拒绝 — 绝不污染文件。
|
|
43
43
|
- **自我修正的验证循环** — 配置 post-edit 钩子(tsc / eslint / 测试),代理会*亲自读取*诊断并在循环内修复;钩子未通过时 `done` 会被阻断。
|
|
44
44
|
- **没有表演的真实门禁** — `ralplan` 共识由真正读取仓库的 critic 子代理执行,`[OKAY]` 裁决被持久化且 `jeo approve` *强制要求*它;`ultragoal` 诚实报告(套件运行只是全局信号,绝不伪造逐条通过)。
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
- **动态步数预算** — 只要近期工具调用展现新的进展就持续延长,停滞时优雅收敛为总结;子代理保持精确的步数契约。
|
|
47
47
|
- **内联 TUI** — 已完成的工作流入真实滚动缓冲区(回合中也可用 tmux 滚轮),代理运行时普通查询输入框仍保持可见并可编辑。Ctrl+O 详细信息切换、主题、剪贴板图片粘贴(Ctrl+V)、CJK/表情安全的宽度计算。
|
|
48
48
|
- **浏览器工具** — 基于 Playwright 的无头 Chromium 自动化,作为一等代理工具:对命名并复用的标签页执行 `open`/`close`/`run`/`act`,优先使用 `observe` 标记的元素 id 而非截图来驱动页面。需要执行一次 `npx playwright install chromium`(未捆绑 — jeo 本身仍是零原生依赖,浏览器二进制文件是 Playwright 的独立下载项)。
|
|
49
|
-
- **远程子代理可见性(Telegram)** — 配对一次机器人(`jeo notify setup`)
|
|
49
|
+
- **远程子代理可见性(Telegram)** — 配对一次机器人(`jeo notify setup`)后,`jeo daemon start` 会在子代理每次状态变化(启动 → 完成/失败/取消)时推送消息,并接受 `/subagents`、`/steer <id> <subagentId> <msg>`、`/cancel <id> <subagentId>` 回传。现在提供 Telegram 论坛主题、内联键盘、图片附件等完整 `gjc` parity;命令仅对配对的聊天授权。
|
|
50
50
|
|
|
51
51
|
## 安装
|
|
52
52
|
|
|
@@ -79,11 +79,12 @@ jeo --tmux # 在独立 tmux 会话中运行
|
|
|
79
79
|
| `/provider login <name>` · `/logout` | 在输入框内 OAuth 登录/登出 |
|
|
80
80
|
| `/agents [role]` · `/subagent` | 按角色(executor/planner/architect/critic)配置模型·thinking·步数 |
|
|
81
81
|
| `/thinking [level]` | 查看/设置默认推理预算(low…xhigh) |
|
|
82
|
-
| `/
|
|
82
|
+
| `/route [status\|on\|off\|why]` | 切换本会话的基于提示词的模型路由 · 解释最近一次路由决策(仅在已配置凭证 — OAuth 或 API 密钥 — 实际可用的模型内自动路由) |
|
|
83
|
+
| `/fast [on\|off\|status]` | 当前模型支持 low 推理时切换 fast thinking 模式 |
|
|
83
84
|
| `/skill` · `$<skill> [intent]` | 列出/运行工作流技能(`$team "任务"` 风格) |
|
|
84
85
|
| `/view` · `/diff` · `/find` · `/search` | 代码查看、git diff、文件/模式搜索 |
|
|
85
86
|
| `/new` · `/resume` · `/sessions` | 会话管理 |
|
|
86
|
-
| `/history [n
|
|
87
|
+
| `/history [n\|all]` · `/export` | 将可读的工作活动历史重新输出到滚动区 · 导出记录 |
|
|
87
88
|
| `/retry` · `/btw <问题>` | 重试上次请求 · 不写入历史的旁路提问 |
|
|
88
89
|
| `/usage` · `/context` · `/compact` | Token 用量、上下文明细、手动压缩 |
|
|
89
90
|
| `/theme` · `/config` · `/help` | 主题、运行时配置、帮助 |
|
|
@@ -212,8 +213,10 @@ JEO_TUI_THEME=cosmic # cosmic/matrix/solar/red-claw/blue-crab/mono/au
|
|
|
212
213
|
JEO_TUI_ALT_SCREEN=1 # 旧版 alt-screen 回合(默认: 内联滚动缓冲)
|
|
213
214
|
JEO_STEP_BASE=24 # 动态步数预算的滚动基数
|
|
214
215
|
JEO_STEP_HARD_CAP=600 # 绝对终止保证
|
|
215
|
-
JEO_STREAM_MAX_MS=
|
|
216
|
+
JEO_STREAM_MAX_MS=1800000 # 整体流截止(默认30分钟; 约束 slow-drip 流,不是为了中断仍在活跃输出的流); 0 表示禁用
|
|
216
217
|
JEO_STREAM_IDLE_MS=300000 # 单次分块空闲上限(默认300秒); 首个 token 前静默较久的慢速/本地后端可调高
|
|
218
|
+
JEO_CALL_TIMEOUT_MS=1800000 # 非流式调用墙钟上限(默认30分钟; compaction/subagents/goal-verify)
|
|
219
|
+
JEO_TURN_MAX_MS=1800000 # 回合停滞预算: 没有工具进展的最长时间(默认30分钟); 0 表示禁用
|
|
217
220
|
JEO_TOOL_OUTPUT_MAX=4000 # 模型可见的工具输出上限(全文溢出到 artifacts)
|
|
218
221
|
```
|
|
219
222
|
|
|
@@ -259,11 +262,11 @@ CI 通过 `.github/workflows/npm-publish.yml` 发布 — GitHub 发布 release
|
|
|
259
262
|
## 更新日志 (Changelog)
|
|
260
263
|
|
|
261
264
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
265
|
+
- **[0.8.3]** (2026-07-08) — "잘못된 모델 설정하는 경우가 있는데" — the static Antigravity catalog had drifted badly from the LIVE Cloud Code Assist agent backend, and several current wire ids actively LIE about their tier: `gemini-3-flash-agent` is really "Gemini 3.5 Flash (High)" (the flagship agent model, which the name-suffix heuristic filed under the SMALL flash tier), `gemini-pro-agent` is really "Gemini 3.1 Pro (High)" (the code-agent model), and `gemini-3.1-pro-high` is DEPRECATED by the backend in favor of `gemini-pro-agent`. Probed the real `fetchAvailableModels` response (displayNames + agentModelSorts + deprecatedModelIds) and made it the single source of truth.
|
|
266
|
+
- **[0.8.2]** (2026-07-08) — "프롬프트라우팅이 api 입력을 요구하면서 동작안해" — prompt routing kept demanding an API key even when the user was already logged in via OAuth (GPT showed the same symptom). Root cause: every routing gate was PROVIDER-level ("any stored credential exists") while OAuth credentials are MODEL-level (ChatGPT OAuth serves only Codex ids, Kimi OAuth only the Kimi Code catalog, gemini OAuth nothing at all since the Cloud Code Assist masquerade was removed) — so routing happily picked models the stored login could never serve, and the turn then failed at call time asking for `<PROVIDER>_API_KEY`. Also restored a missing export that had broken `bun run typecheck` on HEAD.
|
|
262
267
|
- **[0.8.1]** (2026-07-08) — Fix-forward for a functional regression this same session shipped in v0.8.0. This repo's working tree is shared with a concurrent interactive session; `git add`-ing `src/ai/provider-status.ts` for v0.8.0's routing-veto-gate fix also swept up that session's already-modified, uncommitted copy of the SAME file (containing an unrelated design change: gemini OAuth alone no longer serving `google/gemini-*` models) without its companion piece in `src/auth/flows/index.ts` — so v0.8.0 shipped with a test suite (`provider-status.test.ts`) asserting behavior whose supporting data flag never landed. CI caught this immediately post-release: 2 failing tests on the exact tagged commit. Investigated, confirmed the missing piece was real (and independently observable live via `jeo doctor` before this session had made ANY of its own edits — not a stray/speculative change), and completed it.
|
|
263
268
|
- **[0.8.0]** (2026-07-08) — Root-caused a genuine reliability bug in the routing veto gate ("라우트가 동작후 api 키인증오류가나오는데 근본적인 원인 개선해"), then regenerated the animated README mascot to visualize the smart cross-provider routing this release cycle has been building: prompt routing that finds the cheapest usable path, with a "money saver" visual motif.
|
|
264
269
|
- **[0.7.58]** (2026-07-07) — Applied the previous entry's `routing.crossProviderPool` to the real live session config, then deep-researched every catalogued model's actual public release date (2026-07 sourced, per-provider parallel research with citations) to answer "모델 설정은 2026년도 기준으로 딥서치해서 모델리스트 최신순으로 정렬" — while researching, found and fixed a genuine ranking bug this surfaced: `strongestCredentialed`'s tiebreak on a full capability tie (identical thinking/output/context) fell through to a raw alphabetical canonical-id compare, which silently picked the OLDEST model in a same-spec family (`claude-opus-4-6` over `claude-opus-4-7`/`4-8`, purely because `"4-6" < "4-8"` as a string) — confirmed reproducible before the fix, confirmed corrected after.
|
|
265
|
-
- **[0.7.57]** (2026-07-07) — "동급모델간 크로스 프로바이더 라우팅이 동작하게 개선" — v0.7.56's tier auto-select was single-winner by design (always the one cheapest/strongest model), so a user with `roles.smol` pinned (or simply multiple credentialed providers) never saw actual distribution across equivalent-class models. Deep-research-informed redesign: production LLM routers (LiteLLM, Bifrost/VoidLLM) model this as a named "model group" spanning provider deployments with a distribution strategy, not a single deterministic pick — jeo now supports both, opt-in.
|
|
266
|
-
- **[0.7.56]** (2026-07-07) — "서로다른 프로바이더로 급에 따라 나눠서 설정하도록 개선" — PromptRouter's `trivial`/`complex` tiers previously collapsed to `defaultModel` whenever `roles.smol`/`roles.slow`/`routing.tiers.*` were left unconfigured (the documented "safe no-op absent configuration" contract), which meant a user with multiple providers credentialed never actually got cross-provider routing without hand-picking a model per tier. Also the long-term ask: routing must stay correct as jeo's model catalog evolves, never a hand-maintained tier→model table that goes stale.
|
|
267
270
|
|
|
268
271
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
269
272
|
<!-- CHANGELOG:END -->
|
package/package.json
CHANGED
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
* contract for users who never opted in.
|
|
49
49
|
*/
|
|
50
50
|
import { callLlm } from "./loop";
|
|
51
|
-
import { resolveRoleModel } from "../ai/model-manager";
|
|
52
|
-
import { catalogMetadata, MODEL_CATALOG, type CatalogModel } from "../ai/model-catalog";
|
|
51
|
+
import { resolveRoleModel, modelServableWithConfig } from "../ai/model-manager";
|
|
52
|
+
import { catalogMetadata, MODEL_CATALOG, compareReleaseDate, type CatalogModel } from "../ai/model-catalog";
|
|
53
53
|
import { priceForModel } from "../ai/pricing";
|
|
54
54
|
import { tryExtractJsonObject } from "./json";
|
|
55
55
|
import type { Config } from "./state";
|
|
@@ -211,18 +211,41 @@ function isCloudProvider(p: ProviderName): p is AuthProvider {
|
|
|
211
211
|
return p !== "ollama" && p !== "lmstudio";
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
|
|
215
|
+
/** Antigravity OAuth is the Cloud Code Assist lane for Gemini-family OAuth routing.
|
|
216
|
+
* When present, auto-select must prefer provider-qualified `antigravity/*` ids and
|
|
217
|
+
* never silently fall back to public `gemini`/`google-gemini` catalog rows that need
|
|
218
|
+
* a Gemini API key at execution time. A plain `oauth.gemini` token counts because
|
|
219
|
+
* Antigravity deliberately accepts it as a fallback credential. */
|
|
220
|
+
function hasAntigravityOauth(config: RoutingConfig): boolean {
|
|
221
|
+
return !!(config.oauth?.antigravity || config.oauth?.gemini);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function isAutoSelectCandidate(m: CatalogModel, config: RoutingConfig): boolean {
|
|
225
|
+
// Shared model-LEVEL credential gate (the full auth matrix is documented on
|
|
226
|
+
// modelServableWithConfig; keep aligned with resolveCall).
|
|
227
|
+
if (!isCloudProvider(m.provider) || m.limitedAvailability || !modelServableWithConfig(m.provider, m.canonical, config)) return false;
|
|
228
|
+
if (hasAntigravityOauth(config) && m.provider === "gemini") return false;
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
|
|
214
233
|
/** Size class jeo infers for a catalog model, used to group EQUIVALENT models
|
|
215
234
|
* across DIFFERENT providers for pool-based routing (see `tierModelPool`) — the
|
|
216
235
|
* cross-provider counterpart to `cheapestCredentialed`/`strongestCredentialed`'s
|
|
217
|
-
* single-winner selection.
|
|
218
|
-
*
|
|
236
|
+
* single-winner selection. An explicit catalog `sizeClass` wins outright — the
|
|
237
|
+
* authoritative tier for ids whose NAME misleads the suffix heuristic (e.g.
|
|
238
|
+
* Antigravity's `gemini-3-flash-agent` is "Gemini 3.5 Flash (High)", a flagship).
|
|
239
|
+
* Otherwise matches PROVIDER-DECLARED size-tier suffixes (Anthropic's
|
|
240
|
+
* haiku/sonnet/opus, Google's flash/pro, OpenAI's mini) against
|
|
219
241
|
* hyphen/dot-delimited SEGMENTS of the canonical id — NOT a raw substring match,
|
|
220
242
|
* which would false-positive on "gemini" containing "mini" as a substring.
|
|
221
243
|
* `null` for ids with no size-tier suffix at all (gpt-5.5, o3, grok-4.3, kimi-*,
|
|
222
244
|
* glm-*, deepseek-*, minimax-* — providers whose naming doesn't encode size);
|
|
223
245
|
* `tierModelPool` falls those back to a strength-tercile split instead. */
|
|
224
|
-
function sizeClassFor(
|
|
225
|
-
|
|
246
|
+
function sizeClassFor(model: CatalogModel): "small" | "mid" | "large" | null {
|
|
247
|
+
if (model.sizeClass) return model.sizeClass;
|
|
248
|
+
const id = model.canonical.toLowerCase();
|
|
226
249
|
const bare = id.includes("/") ? id.slice(id.indexOf("/") + 1) : id;
|
|
227
250
|
const segments = bare.split(/[-.]/);
|
|
228
251
|
if (segments.includes("haiku") || segments.includes("flash") || segments.includes("mini") || segments.includes("nano")) return "small";
|
|
@@ -233,21 +256,6 @@ function sizeClassFor(canonical: string): "small" | "mid" | "large" | null {
|
|
|
233
256
|
|
|
234
257
|
const TIER_TO_SIZE_CLASS: Record<PromptTier, "small" | "mid" | "large"> = { trivial: "small", standard: "mid", high: "mid", complex: "large" };
|
|
235
258
|
|
|
236
|
-
/** Compares `releaseDate` ("YYYY-MM", lexicographically sortable). `>0` when `a`
|
|
237
|
-
* is NEWER than `b`. A missing date sorts as OLDEST on that side — an
|
|
238
|
-
* unconfirmed date can never silently outrank a verified-newer model. Shared
|
|
239
|
-
* recency tiebreak for `compareStrengthAscending`/`cheapestCredentialed`/
|
|
240
|
-
* `strongestCredentialed`, replacing the arbitrary alphabetical canonical-id
|
|
241
|
-
* tiebreak those used before (which, on a full capability tie, picked the
|
|
242
|
-
* OLDEST id alphabetically — e.g. `claude-opus-4-6` beating `claude-opus-4-8`
|
|
243
|
-
* as "strongest" purely because "4-6" < "4-8" as a string). */
|
|
244
|
-
function compareRecency(a: CatalogModel, b: CatalogModel): number {
|
|
245
|
-
if (a.releaseDate === b.releaseDate) return 0;
|
|
246
|
-
if (!a.releaseDate) return -1;
|
|
247
|
-
if (!b.releaseDate) return 1;
|
|
248
|
-
return a.releaseDate < b.releaseDate ? -1 : 1;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
259
|
/** Multi-key strength comparator (ascending: weakest first) — same ranking signals
|
|
252
260
|
* as `strongestCredentialed`'s single-winner tiebreak, reused here to bucket
|
|
253
261
|
* UNCLASSIFIED (no size-suffix) models into a tercile fallback. */
|
|
@@ -257,7 +265,7 @@ function compareStrengthAscending(a: CatalogModel, b: CatalogModel): number {
|
|
|
257
265
|
if (xhighA !== xhighB) return xhighA - xhighB;
|
|
258
266
|
if (a.maxOutputTokens !== b.maxOutputTokens) return a.maxOutputTokens - b.maxOutputTokens;
|
|
259
267
|
if (a.contextTokens !== b.contextTokens) return a.contextTokens - b.contextTokens;
|
|
260
|
-
const recency =
|
|
268
|
+
const recency = compareReleaseDate(a.releaseDate, b.releaseDate);
|
|
261
269
|
if (recency !== 0) return recency;
|
|
262
270
|
return a.canonical < b.canonical ? -1 : a.canonical > b.canonical ? 1 : 0;
|
|
263
271
|
}
|
|
@@ -272,11 +280,11 @@ function compareStrengthAscending(a: CatalogModel, b: CatalogModel): number {
|
|
|
272
280
|
* for deterministic pool ordering (required by `selectFromPool`'s index math). */
|
|
273
281
|
export function tierModelPool(tier: PromptTier, config: RoutingConfig): string[] {
|
|
274
282
|
const targetClass = TIER_TO_SIZE_CLASS[tier];
|
|
275
|
-
const credentialed = MODEL_CATALOG.filter(m =>
|
|
283
|
+
const credentialed = MODEL_CATALOG.filter(m => isAutoSelectCandidate(m, config));
|
|
276
284
|
|
|
277
|
-
const suffixPool = credentialed.filter(m => sizeClassFor(m
|
|
285
|
+
const suffixPool = credentialed.filter(m => sizeClassFor(m) === targetClass);
|
|
278
286
|
|
|
279
|
-
const unclassified = credentialed.filter(m => sizeClassFor(m
|
|
287
|
+
const unclassified = credentialed.filter(m => sizeClassFor(m) === null).sort(compareStrengthAscending);
|
|
280
288
|
const third = Math.ceil(unclassified.length / 3);
|
|
281
289
|
// "high" shares "standard"'s unclassified-model tercile share: the catalog's
|
|
282
290
|
// size-suffix classification only has 3 real buckets (small/mid/large — see
|
|
@@ -322,12 +330,12 @@ export function cheapestCredentialed(config: RoutingConfig): string | null {
|
|
|
322
330
|
let best: CatalogModel | null = null;
|
|
323
331
|
let bestCost = Infinity;
|
|
324
332
|
for (const m of MODEL_CATALOG) {
|
|
325
|
-
//
|
|
326
|
-
//
|
|
327
|
-
//
|
|
328
|
-
// real veto gate in launch.ts's `runTurn`
|
|
329
|
-
// live OAuth expiry) before a turn
|
|
330
|
-
if (!
|
|
333
|
+
// Candidate eligibility is centralized in `isAutoSelectCandidate`: it keeps
|
|
334
|
+
// OAuth-backed Antigravity routing on provider-qualified 3.1+ Gemini rows and
|
|
335
|
+
// prevents public `gemini` catalog rows from winning when Antigravity OAuth is
|
|
336
|
+
// the available Gemini lane. The real veto gate in launch.ts's `runTurn` still
|
|
337
|
+
// re-verifies full readiness (including live OAuth expiry) before a turn uses it.
|
|
338
|
+
if (!isAutoSelectCandidate(m, config)) continue;
|
|
331
339
|
const price = priceForModel(m.canonical);
|
|
332
340
|
if (!price) continue;
|
|
333
341
|
const cost = price.inPerM + price.outPerM;
|
|
@@ -335,8 +343,18 @@ export function cheapestCredentialed(config: RoutingConfig): string | null {
|
|
|
335
343
|
best = m;
|
|
336
344
|
bestCost = cost;
|
|
337
345
|
} else if (cost === bestCost && best) {
|
|
338
|
-
|
|
339
|
-
|
|
346
|
+
// Same price (common inside one subscription family, e.g. every Antigravity
|
|
347
|
+
// Gemini row shares the flat "gemini" price): prefer the SMALLER declared
|
|
348
|
+
// size class first — "cheapest" is the trivial tier's pick, and a family's
|
|
349
|
+
// Low/(High) variants are priced identically while consuming very different
|
|
350
|
+
// quota — then NEWER release, then canonical id (deterministic).
|
|
351
|
+
const sizeRank = (m2: CatalogModel): number => {
|
|
352
|
+
const cls = sizeClassFor(m2);
|
|
353
|
+
return cls === "small" ? 0 : cls === "mid" ? 1 : cls === "large" ? 2 : 3;
|
|
354
|
+
};
|
|
355
|
+
const bySize = sizeRank(m) - sizeRank(best);
|
|
356
|
+
const recency = compareReleaseDate(m.releaseDate, best.releaseDate);
|
|
357
|
+
if (bySize < 0 || (bySize === 0 && (recency > 0 || (recency === 0 && m.canonical < best.canonical)))) best = m;
|
|
340
358
|
}
|
|
341
359
|
}
|
|
342
360
|
return best?.canonical ?? null;
|
|
@@ -360,8 +378,9 @@ export function strongestCredentialed(
|
|
|
360
378
|
): string | null {
|
|
361
379
|
let best: CatalogModel | null = null;
|
|
362
380
|
for (const m of MODEL_CATALOG) {
|
|
363
|
-
//
|
|
364
|
-
|
|
381
|
+
// Candidate eligibility is centralized in `isAutoSelectCandidate` (see
|
|
382
|
+
// cheapestCredentialed above for the Antigravity/Gemini OAuth rationale).
|
|
383
|
+
if (!isAutoSelectCandidate(m, config)) continue;
|
|
365
384
|
if (filter && !filter(m)) continue;
|
|
366
385
|
if (!best) { best = m; continue; }
|
|
367
386
|
const xhighM = m.thinking.includes("xhigh") ? 1 : 0;
|
|
@@ -369,7 +388,7 @@ export function strongestCredentialed(
|
|
|
369
388
|
if (xhighM !== xhighBest) { if (xhighM > xhighBest) best = m; continue; }
|
|
370
389
|
if (m.maxOutputTokens !== best.maxOutputTokens) { if (m.maxOutputTokens > best.maxOutputTokens) best = m; continue; }
|
|
371
390
|
if (m.contextTokens !== best.contextTokens) { if (m.contextTokens > best.contextTokens) best = m; continue; }
|
|
372
|
-
const recency =
|
|
391
|
+
const recency = compareReleaseDate(m.releaseDate, best.releaseDate);
|
|
373
392
|
if (recency !== 0) { if (recency > 0) best = m; continue; }
|
|
374
393
|
if (m.canonical < best.canonical) best = m;
|
|
375
394
|
}
|
|
@@ -388,7 +407,7 @@ export function strongestCredentialed(
|
|
|
388
407
|
* `roles.medium`/`defaultModel` — never to `complex`'s flagship pick). */
|
|
389
408
|
function strongestMidTierCredentialed(config: RoutingConfig): string | null {
|
|
390
409
|
const midClass = MODEL_CATALOG.filter(
|
|
391
|
-
m =>
|
|
410
|
+
m => isAutoSelectCandidate(m, config) && sizeClassFor(m) === "mid",
|
|
392
411
|
);
|
|
393
412
|
if (midClass.length === 0) return null;
|
|
394
413
|
return midClass.sort(compareStrengthAscending).at(-1)!.canonical;
|
|
@@ -30,7 +30,7 @@ export interface ModelCatalogEntry {
|
|
|
30
30
|
* actual top tier, claude-fable-5, is pricier than the "recommended" sonnet-5 —
|
|
31
31
|
* recommended means "solid everyday default", not "strongest"). Re-verify and
|
|
32
32
|
* update whenever a provider ships a new default-tier release. */
|
|
33
|
-
const RECOMMENDED: Record<string, true> = { "claude-sonnet-5": true, "gpt-5.5": true, "gemini-3-flash": true, "antigravity/
|
|
33
|
+
const RECOMMENDED: Record<string, true> = { "claude-sonnet-5": true, "gpt-5.5": true, "gemini-3-flash": true, "antigravity/gemini-pro-agent": true, "qwen2.5": true };
|
|
34
34
|
|
|
35
35
|
export function normalizeModelId(id: string): string {
|
|
36
36
|
return (id ?? "").trim().toLowerCase();
|
package/src/ai/model-catalog.ts
CHANGED
|
@@ -45,34 +45,31 @@ export interface CatalogModel {
|
|
|
45
45
|
* date — these sort AFTER every dated entry within their block, never before,
|
|
46
46
|
* so an unconfirmed date can never silently outrank a verified-newer model. */
|
|
47
47
|
releaseDate?: string;
|
|
48
|
+
/** Explicit routing size class, for ids whose NAME misleads the suffix
|
|
49
|
+
* heuristic in prompt-router.ts's `sizeClassFor` (e.g. Antigravity's wire id
|
|
50
|
+
* `gemini-3-flash-agent` is actually "Gemini 3.5 Flash (High)" — a flagship,
|
|
51
|
+
* not a small flash tier). Unset = derive from the name suffix as before. */
|
|
52
|
+
sizeClass?: "small" | "mid" | "large";
|
|
48
53
|
}
|
|
49
54
|
|
|
50
55
|
const FULL: ThinkLevel[] = ["low", "medium", "high", "xhigh"];
|
|
51
56
|
const STD: ThinkLevel[] = ["low", "medium", "high"];
|
|
52
57
|
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
["gemini-3-flash", "2025-12"],
|
|
69
|
-
["gemini-3-pro-high", "2025-11"],
|
|
70
|
-
["gemini-3-pro-low", "2025-11"],
|
|
71
|
-
["gpt-oss-120b-medium", "2025-08"],
|
|
72
|
-
["gemini-2.5-flash", "2025-06"],
|
|
73
|
-
["gemini-2.5-flash-thinking", "2025-06"],
|
|
74
|
-
["gemini-2.5-pro", "2025-03"],
|
|
75
|
-
] as const;
|
|
58
|
+
// Antigravity (Cloud Code Assist) agent-mode model rows, verified against the
|
|
59
|
+
// LIVE fetchAvailableModels response (agentModelSorts "Recommended" group +
|
|
60
|
+
// per-id displayName, probed 2026-07-08). CRITICAL: several wire ids do NOT
|
|
61
|
+
// mean what their name suggests — the displayName is the truth:
|
|
62
|
+
// gemini-3-flash-agent → "Gemini 3.5 Flash (High)" (flagship agent tier)
|
|
63
|
+
// gemini-3.5-flash-low → "Gemini 3.5 Flash (Medium)"
|
|
64
|
+
// gemini-3.5-flash-extra-low→ "Gemini 3.5 Flash (Low)"
|
|
65
|
+
// gemini-pro-agent → "Gemini 3.1 Pro (High)" (the code-agent model;
|
|
66
|
+
// replaces DEPRECATED gemini-3.1-pro-high, per the
|
|
67
|
+
// backend's deprecatedModelIds.newModelId mapping —
|
|
68
|
+
// BUILTIN_ALIASES keeps the old id working)
|
|
69
|
+
// claude-sonnet-4-6 → "Claude Sonnet 4.6 (Thinking)"
|
|
70
|
+
// `sizeClass` pins each row's routing tier to that display truth so the name
|
|
71
|
+
// suffix heuristic can't misfile them (releaseDate = the underlying base-model
|
|
72
|
+
// announcement; Antigravity is a hosting layer, not a separate release).
|
|
76
73
|
|
|
77
74
|
/** A curated set of common public models with their documented capabilities.
|
|
78
75
|
* Ordered NEWEST-FIRST within each provider block per `releaseDate`
|
|
@@ -139,21 +136,22 @@ export const MODEL_CATALOG: readonly CatalogModel[] = [
|
|
|
139
136
|
{ canonical: "gemini-2.5-pro", provider: "gemini", providerModel: "gemini-2.5-pro", contextTokens: 1_000_000, maxOutputTokens: 65_536, thinking: STD, images: true, releaseDate: "2025-03" },
|
|
140
137
|
{ canonical: "gemini-2.0-flash", provider: "gemini", providerModel: "gemini-2.0-flash", contextTokens: 1_000_000, maxOutputTokens: 8_192, thinking: [], images: true, releaseDate: "2025-02" },
|
|
141
138
|
{ canonical: "gemini-1.5-pro", provider: "gemini", providerModel: "gemini-1.5-pro", contextTokens: 1_000_000, maxOutputTokens: 8_192, thinking: [], images: true, releaseDate: "2024-02" },
|
|
142
|
-
// Google Antigravity /
|
|
143
|
-
// collisions with public Gemini
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
139
|
+
// Google Antigravity / Cloud Code Assist agent set — provider-qualified to avoid
|
|
140
|
+
// collisions with public Gemini/Anthropic ids. Rows mirror the LIVE
|
|
141
|
+
// fetchAvailableModels agent list (see the block comment above ANTIGRAVITY rows'
|
|
142
|
+
// sizeClass rationale near the top of this file). Newest first.
|
|
143
|
+
// Gemini 3.5 Flash tiers (base model released 2026-05, Google I/O):
|
|
144
|
+
{ canonical: "antigravity/gemini-3-flash-agent", provider: "antigravity", providerModel: "gemini-3-flash-agent", contextTokens: 1_000_000, maxOutputTokens: 65_536, thinking: FULL, images: true, company: "Google Antigravity", releaseDate: "2026-05", sizeClass: "large" }, // "Gemini 3.5 Flash (High)" — the HIGH agent model
|
|
145
|
+
{ canonical: "antigravity/gemini-3.5-flash-low", provider: "antigravity", providerModel: "gemini-3.5-flash-low", contextTokens: 1_000_000, maxOutputTokens: 65_536, thinking: FULL, images: true, company: "Google Antigravity", releaseDate: "2026-05", sizeClass: "mid" }, // "Gemini 3.5 Flash (Medium)"
|
|
146
|
+
{ canonical: "antigravity/gemini-3.5-flash-extra-low", provider: "antigravity", providerModel: "gemini-3.5-flash-extra-low", contextTokens: 1_000_000, maxOutputTokens: 65_536, thinking: FULL, images: true, company: "Google Antigravity", releaseDate: "2026-05", sizeClass: "small" }, // "Gemini 3.5 Flash (Low)"
|
|
147
|
+
// Gemini 3.1 Pro tiers (base model released 2026-02):
|
|
148
|
+
{ canonical: "antigravity/gemini-pro-agent", provider: "antigravity", providerModel: "gemini-pro-agent", contextTokens: 1_000_000, maxOutputTokens: 65_536, thinking: FULL, images: true, company: "Google Antigravity", releaseDate: "2026-02", sizeClass: "mid" }, // "Gemini 3.1 Pro (High)" — the CODE-AGENT model (replaces deprecated gemini-3.1-pro-high)
|
|
149
|
+
{ canonical: "antigravity/gemini-3.1-pro-low", provider: "antigravity", providerModel: "gemini-3.1-pro-low", contextTokens: 1_000_000, maxOutputTokens: 65_536, thinking: FULL, images: true, company: "Google Antigravity", releaseDate: "2026-02", sizeClass: "small" }, // "Gemini 3.1 Pro (Low)" — the LOW agent model
|
|
150
|
+
// Anthropic via Antigravity (wire id claude-sonnet-4-6 IS the thinking variant per its displayName):
|
|
151
|
+
{ canonical: "antigravity/claude-sonnet-4-6", provider: "antigravity", providerModel: "claude-sonnet-4-6", contextTokens: 200_000, maxOutputTokens: 64_000, thinking: FULL, images: true, company: "Anthropic via Antigravity", releaseDate: "2026-02", sizeClass: "mid" }, // "Claude Sonnet 4.6 (Thinking)"
|
|
152
|
+
{ canonical: "antigravity/claude-opus-4-6-thinking", provider: "antigravity", providerModel: "claude-opus-4-6-thinking", contextTokens: 200_000, maxOutputTokens: 64_000, thinking: FULL, images: true, company: "Anthropic via Antigravity", releaseDate: "2026-02", sizeClass: "large" }, // "Claude Opus 4.6 (Thinking)"
|
|
153
|
+
// OpenAI OSS via Antigravity:
|
|
154
|
+
{ canonical: "antigravity/gpt-oss-120b-medium", provider: "antigravity", providerModel: "gpt-oss-120b-medium", contextTokens: 1_000_000, maxOutputTokens: 65_536, thinking: FULL, images: false, company: "OpenAI via Antigravity", releaseDate: "2025-08", sizeClass: "mid" }, // "GPT-OSS 120B (Medium)"
|
|
157
155
|
// Tencent — hosts the same underlying models under its own cloud brand; dates
|
|
158
156
|
// below are the ORIGINAL developer's release (DeepSeek/MiniMax/Zhipu/Moonshot),
|
|
159
157
|
// not Tencent's hosting-catalog appearance date, except where the id itself pins
|
|
@@ -201,6 +199,13 @@ export const KIMI_CODE_MODELS: readonly string[] = [
|
|
|
201
199
|
"kimi-k2",
|
|
202
200
|
];
|
|
203
201
|
|
|
202
|
+
/** Recency compare for `releaseDate` ("YYYY-MM"): `>0` when `a` is newer; a
|
|
203
|
+
* missing date sorts oldest, so an unconfirmed date never outranks a
|
|
204
|
+
* verified-newer model. */
|
|
205
|
+
export function compareReleaseDate(a: string | undefined, b: string | undefined): number {
|
|
206
|
+
return (a ?? "").localeCompare(b ?? "");
|
|
207
|
+
}
|
|
208
|
+
|
|
204
209
|
/** Format a token count compactly (1000 → 1K, 1_000_000 → 1M). */
|
|
205
210
|
export function formatTokens(n: number): string {
|
|
206
211
|
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(n % 1_000_000 === 0 ? 0 : 1)}M`;
|
|
@@ -285,6 +285,9 @@ export function catalogOr(result: ProviderModelsResult): ProviderModelsResult {
|
|
|
285
285
|
// Antigravity's available models depend on the Cloud Code Assist agent backend
|
|
286
286
|
// and must never be faked from a hard-coded catalog.
|
|
287
287
|
if (result.provider === "antigravity") return result;
|
|
288
|
+
// No catalog-fallback for oauth-sourced gemini failures: OAuth can LIST but
|
|
289
|
+
// not SERVE gemini models (see listProviderModels' gemini gate).
|
|
290
|
+
if (result.provider === "gemini" && result.source === "oauth") return result;
|
|
288
291
|
// OAuth: OpenAI/Codex legitimately rejects the standard /models endpoint while
|
|
289
292
|
// the fixed Codex ids still work; other OAuth providers fall back to their full
|
|
290
293
|
// static catalog too. API-key providers only fall back when the provider has NO
|
|
@@ -362,6 +365,15 @@ export async function listProviderModels(
|
|
|
362
365
|
cred = { kind: "api_key", provider: prov, token: config.providers[prov]! };
|
|
363
366
|
source = "api_key";
|
|
364
367
|
}
|
|
368
|
+
// Gemini models are exposed ONLY under API-key auth: a gemini OAuth token can
|
|
369
|
+
// still list models from generativelanguage.googleapis.com, but it can no
|
|
370
|
+
// longer SERVE them (the gemini-cli/Cloud Code Assist masquerade was removed
|
|
371
|
+
// — see OAUTH_FLOW_REGISTRY.gemini.verifiedEndToEnd), so surfacing them in
|
|
372
|
+
// pickers sells models every call would reject. The API-key swap above
|
|
373
|
+
// already ran, so reaching here with an oauth credential means NO key exists.
|
|
374
|
+
if (provider === "gemini" && cred.kind === "oauth") {
|
|
375
|
+
return { provider, models: [], ok: false, source, error: "OAuth cannot serve gemini models — set GEMINI_API_KEY, or use antigravity/* (Cloud Code Assist)" };
|
|
376
|
+
}
|
|
365
377
|
const isLocalOpenAi = provider === "openai" && !!(opts.baseUrl ?? process.env.OPENAI_BASE_URL);
|
|
366
378
|
if (source === "none" && !isLocalOpenAi) {
|
|
367
379
|
return { provider, models: [], ok: false, source, error: "not logged in" };
|
package/src/ai/model-manager.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { readGlobalConfig } from "../agent/state";
|
|
|
4
4
|
import { resolveCredential, isOAuthProvider, type AuthProvider, type Credential } from "../auth";
|
|
5
5
|
import "./register-providers"; // side-effect: registers built-in adapters into providerRegistry
|
|
6
6
|
import type { CallOptions, Message, ProviderAdapter, ProviderName } from "./types";
|
|
7
|
-
import { expandAlias, resolveModelId, effectiveAliasesFor } from "./model-registry";
|
|
7
|
+
import { expandAlias, resolveModelId, effectiveAliasesFor, BUILTIN_ALIASES } from "./model-registry";
|
|
8
8
|
import { findCatalogEntry, type ModelCatalogEntry } from "./model-catalog-compat";
|
|
9
9
|
import { toProviderModel, CODEX_MODELS, KIMI_CODE_MODELS, findCatalogModel } from "./model-catalog";
|
|
10
10
|
import { xaiCredential } from "./providers/xai";
|
|
@@ -118,7 +118,7 @@ export function resolveMaxOutputTokens(
|
|
|
118
118
|
model?: string,
|
|
119
119
|
level?: "low" | "medium" | "high" | "xhigh",
|
|
120
120
|
): number {
|
|
121
|
-
const meta = model ? findCatalogEntry(expandAlias(model,
|
|
121
|
+
const meta = model ? findCatalogEntry(expandAlias(model, BUILTIN_ALIASES)) : undefined;
|
|
122
122
|
if (!meta) return thinkingMaxTokens(level);
|
|
123
123
|
const envCap = Number(jeoEnv("MAX_OUTPUT_TOKENS"));
|
|
124
124
|
const cap = Number.isFinite(envCap) && envCap > 0 ? envCap : DEFAULT_MAX_OUTPUT_CAP;
|
|
@@ -198,7 +198,6 @@ export interface ModelManager {
|
|
|
198
198
|
resolveProvider: typeof resolveProvider;
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
const ALIAS_DEFAULTS = { fast: "ollama/qwen2.5:0.5b", local: "ollama/qwen2.5:0.5b", sonnet: "claude-sonnet-4-6", opus: "claude-opus-4-6", haiku: "claude-haiku-4-5", gpt: "gpt-5.5", flash: "gemini-2.5-flash", grok: "grok-4.3" };
|
|
202
201
|
|
|
203
202
|
/**
|
|
204
203
|
* Build retry options from a config `retry` budget (gjc parity). `requestMaxRetries`
|
|
@@ -307,7 +306,12 @@ export function resolveRetryOptions(retry: Config["retry"], kind: "request" | "s
|
|
|
307
306
|
* end-to-end.
|
|
308
307
|
*/
|
|
309
308
|
function oauthServesModel(provider: AuthProvider, model: string): boolean {
|
|
310
|
-
if (provider === "openai")
|
|
309
|
+
if (provider === "openai") {
|
|
310
|
+
// Provider-qualified ids (`openai/gpt-5.5`) are valid route targets — the
|
|
311
|
+
// adapters strip the prefix on the wire, so servability must match.
|
|
312
|
+
const wire = model.startsWith("openai/") ? model.slice(7) : model;
|
|
313
|
+
return CODEX_MODELS.includes(wire);
|
|
314
|
+
}
|
|
311
315
|
if (provider === "kimi") {
|
|
312
316
|
const wire = model.startsWith("kimi/") ? model.slice(5) : model;
|
|
313
317
|
return KIMI_CODE_MODELS.includes(wire);
|
|
@@ -316,6 +320,32 @@ function oauthServesModel(provider: AuthProvider, model: string): boolean {
|
|
|
316
320
|
return true;
|
|
317
321
|
}
|
|
318
322
|
|
|
323
|
+
/**
|
|
324
|
+
* Whether jeo's STORED credentials can actually serve `model` — the shared
|
|
325
|
+
* model-LEVEL gate behind auto-routing candidacy (prompt-router's
|
|
326
|
+
* `isAutoSelectCandidate`) and launch.ts's routing veto. Mirrors `resolveCall`'s
|
|
327
|
+
* real rules instead of the provider-level "any credential exists" check that
|
|
328
|
+
* let OAuth-only OpenAI route to non-Codex models (then fail at call time with
|
|
329
|
+
* "set OPENAI_API_KEY" — the exact key-demand-despite-OAuth bug):
|
|
330
|
+
* - local providers (ollama/lmstudio) are keyless — always servable;
|
|
331
|
+
* - antigravity is OAuth-ONLY (its own login or the gemini OAuth fallback);
|
|
332
|
+
* an `providers.antigravity` API key cannot serve calls (resolveCall throws);
|
|
333
|
+
* - an API key serves the provider's FULL catalog (API keys are never
|
|
334
|
+
* model-scoped);
|
|
335
|
+
* - OAuth serves exactly what `oauthServesModel` says it serves end-to-end
|
|
336
|
+
* (openai→Codex ids only, kimi→Kimi Code ids only, gemini→nothing);
|
|
337
|
+
* - a configured OpenAI base URL is the keyless local-proxy path — any model.
|
|
338
|
+
*/
|
|
339
|
+
export function modelServableWithConfig(provider: ProviderName, model: string, config: Partial<Pick<Config, "providers" | "oauth" | "openaiBaseUrl">>): boolean {
|
|
340
|
+
if (provider === "ollama" || provider === "lmstudio") return true;
|
|
341
|
+
if (provider === "antigravity") return !!(config.oauth?.antigravity || config.oauth?.gemini);
|
|
342
|
+
if (provider === "openai" && config.openaiBaseUrl) return true;
|
|
343
|
+
const auth = provider as AuthProvider;
|
|
344
|
+
if (config.providers?.[auth]) return true;
|
|
345
|
+
if (config.oauth?.[auth]) return oauthServesModel(auth, model);
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
|
|
319
349
|
/**
|
|
320
350
|
* Pick the credential to actually use for a provider call / live discovery.
|
|
321
351
|
* OAuth is the user's explicit login, so it wins whenever the bundled adapter can
|
|
@@ -377,7 +407,7 @@ export function credentialForCall(
|
|
|
377
407
|
async function resolveCall(options: Partial<CallOptions>, kind: "request" | "stream" = "request"): Promise<Resolved> {
|
|
378
408
|
const config = await readGlobalConfig();
|
|
379
409
|
const aliases = { ...((config as { modelAliases?: Record<string, string> }).modelAliases ?? {}) };
|
|
380
|
-
const model = expandAlias(options.model ?? config.defaultModel, { ...
|
|
410
|
+
const model = expandAlias(options.model ?? config.defaultModel, { ...BUILTIN_ALIASES, ...aliases });
|
|
381
411
|
const provider = resolveProvider(model);
|
|
382
412
|
const adapter = providerRegistry.get(provider)!;
|
|
383
413
|
|
|
@@ -447,7 +477,7 @@ async function resolveCall(options: Partial<CallOptions>, kind: "request" | "str
|
|
|
447
477
|
const credential = await resolveCredential(credentialProvider);
|
|
448
478
|
const effective = effectiveCredentialForProvider(credentialProvider, credential, config, model);
|
|
449
479
|
const isLocalOpenAi = provider === "openai" && !!baseUrl;
|
|
450
|
-
if (provider === "openai" && effective.kind === "oauth" && !isLocalOpenAi && !
|
|
480
|
+
if (provider === "openai" && effective.kind === "oauth" && !isLocalOpenAi && !oauthServesModel("openai", model)) {
|
|
451
481
|
throw new Error(
|
|
452
482
|
"OpenAI OAuth 자격증명은 Codex 모델(gpt-5.5/gpt-5.4)만 지원. OPENAI_API_KEY를 설정하거나 모델을 변경하세요"
|
|
453
483
|
);
|
package/src/ai/model-registry.ts
CHANGED
|
@@ -16,6 +16,10 @@ export const BUILTIN_ALIASES: ModelAliases = {
|
|
|
16
16
|
flash: "gemini-2.5-flash",
|
|
17
17
|
grok: "grok-4.3",
|
|
18
18
|
kimi: "kimi-k2-0711-preview",
|
|
19
|
+
// Antigravity's backend DEPRECATED this wire id (deprecatedModelIds:
|
|
20
|
+
// { "gemini-3.1-pro-high": { newModelId: "gemini-pro-agent" } }) — keep
|
|
21
|
+
// existing configs/roles pinned to the old id working on the successor.
|
|
22
|
+
"antigravity/gemini-3.1-pro-high": "antigravity/gemini-pro-agent",
|
|
19
23
|
};
|
|
20
24
|
|
|
21
25
|
// Expand an alias to a concrete model id. Unknown input passes through unchanged.
|
package/src/commands/launch.ts
CHANGED
|
@@ -45,7 +45,7 @@ import { callLlm, type Message } from "../agent/loop";
|
|
|
45
45
|
import { friendlyProviderError } from "../util/provider-error";
|
|
46
46
|
import { readGlobalConfig, saveConfigPatch, resolveWikiRoot } from "../agent/state";
|
|
47
47
|
import { rememberModelPatch, recentModelsForDisplay } from "../agent/model-recency";
|
|
48
|
-
import { describeModel, describeAllProviders, describeProvider, resolveProvider, thinkingMaxTokens, resolveMaxOutputTokens, thinkingToReasoningEffort, discoverModels, flattenModels, resolveSelection, catalogMetadata, catalogByProvider, resolveRoleModel, CODEX_MODELS, qualifyModelId } from "../ai";
|
|
48
|
+
import { describeModel, describeAllProviders, describeProvider, resolveProvider, thinkingMaxTokens, resolveMaxOutputTokens, thinkingToReasoningEffort, discoverModels, flattenModels, resolveSelection, catalogMetadata, catalogByProvider, resolveRoleModel, CODEX_MODELS, qualifyModelId, modelServableWithConfig } from "../ai";
|
|
49
49
|
import type { ProviderModelsResult, PickEntry, ProviderName, ModelRole, ThinkLevel } from "../ai";
|
|
50
50
|
import { readGoalState, writeGoalState, clearGoalState, verifyGoal } from "../agent/goal-verifier";
|
|
51
51
|
import { routePrompt, deriveCacheSessionKey, warnOnce, type RouteDecision } from "../agent/prompt-router";
|
|
@@ -905,7 +905,10 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
905
905
|
let routeCredentialNotice: string | undefined;
|
|
906
906
|
let routeVetoNote: string | undefined;
|
|
907
907
|
if (routed) {
|
|
908
|
-
|
|
908
|
+
// describeModel expands aliases (e.g. a hand-written `roles.high: "gpt"`)
|
|
909
|
+
// exactly like the real call path, so the servability check below judges
|
|
910
|
+
// the id the provider will actually see — never a raw alias string.
|
|
911
|
+
const { resolved: routedResolved, provider: routedProvider } = await describeModel(routed.model, turnConfig);
|
|
909
912
|
const status = await describeProvider(routedProvider, turnConfig);
|
|
910
913
|
if (!status.ready) {
|
|
911
914
|
routeVetoNote = `routed model '${routed.model}' (${routedProvider}) has no usable credential this turn — fell back to '${sessionModel || defaultModel}'`;
|
|
@@ -914,6 +917,17 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
914
917
|
`[route] routed to '${routed.model}' (${routedProvider}) but that provider has no usable credential — falling back to the default model this turn. Run 'jeo auth login ${routedProvider}' or reconfigure routing.tiers/roles for a provider you're logged into.`,
|
|
915
918
|
);
|
|
916
919
|
routed = null;
|
|
920
|
+
} else if (!modelServableWithConfig(routedProvider, routedResolved, turnConfig)) {
|
|
921
|
+
// Provider readiness is necessary but not sufficient: an OAuth login only
|
|
922
|
+
// serves specific models (auth matrix on modelServableWithConfig). Only
|
|
923
|
+
// user-pinned routing.tiers/roles models can hit this branch —
|
|
924
|
+
// auto-selected models already passed the same predicate.
|
|
925
|
+
routeVetoNote = `routed model '${routed.model}' (${routedProvider}) is not servable by the stored ${routedProvider} credential this turn — fell back to '${sessionModel || defaultModel}'`;
|
|
926
|
+
routeCredentialNotice = warnOnce(
|
|
927
|
+
`prompt-router:model-not-servable:${routed.model}`,
|
|
928
|
+
`[route] routed to '${routed.model}' but the stored ${routedProvider} OAuth login cannot serve that model — falling back to the default model this turn. Set ${routedProvider.toUpperCase()}_API_KEY or reconfigure routing.tiers/roles to a model your login serves.`,
|
|
929
|
+
);
|
|
930
|
+
routed = null;
|
|
917
931
|
}
|
|
918
932
|
}
|
|
919
933
|
lastRouteDecision = routed ?? (routeVetoNote ? { note: routeVetoNote } : routingEnabled && !sessionModel ? { note: "routing produced no decision (fail-open)" } : { note: "routing not active this turn" });
|