jeo-code 0.6.22 → 0.6.23
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 +15 -0
- package/README.ja.md +5 -1
- package/README.ko.md +5 -1
- package/README.md +5 -1
- package/README.zh.md +5 -1
- package/package.json +1 -1
- package/src/agent/config-schema.ts +12 -0
- package/src/agent/session.ts +10 -3
- package/src/agent/state.ts +19 -14
- package/src/ai/index.ts +1 -0
- package/src/ai/model-catalog.ts +121 -1
- package/src/ai/model-discovery.ts +55 -3
- package/src/ai/model-manager.ts +43 -11
- package/src/ai/model-registry.ts +2 -0
- package/src/ai/provider-status.ts +26 -7
- package/src/ai/providers/anthropic-compatible.ts +27 -0
- package/src/ai/providers/anthropic.ts +3 -1
- package/src/ai/providers/antigravity.ts +31 -6
- package/src/ai/providers/gemini.ts +45 -4
- package/src/ai/providers/kimi.ts +18 -0
- package/src/ai/providers/lmstudio.ts +8 -0
- package/src/ai/providers/ollama.ts +17 -5
- package/src/ai/providers/openai-compatible-catalog.ts +72 -0
- package/src/ai/providers/openai-compatible.ts +31 -0
- package/src/ai/providers/openai.ts +23 -7
- package/src/ai/providers/xai.ts +18 -0
- package/src/ai/register-providers.ts +18 -0
- package/src/ai/think-tags.ts +84 -0
- package/src/ai/types.ts +6 -1
- package/src/auth/flows/index.ts +3 -3
- package/src/auth/index.ts +4 -1
- package/src/auth/oauth.ts +3 -3
- package/src/auth/refresh.ts +5 -0
- package/src/auth/storage.ts +12 -1
- package/src/commands/auth.ts +19 -2
- package/src/commands/launch/flags.ts +5 -1
- package/src/commands/launch/input.ts +13 -0
- package/src/commands/launch.ts +78 -12
- package/src/commands/setup.ts +3 -2
- package/src/tui/app.ts +51 -31
- package/src/tui/components/ascii-art.ts +11 -7
- package/src/tui/components/autocomplete.ts +16 -0
- package/src/tui/components/forge.ts +1 -1
- package/src/tui/components/transcript.ts +7 -0
- package/src/tui/components/width.ts +21 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,21 @@ 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.6.23] - 2026-06-19
|
|
10
|
+
_Live reasoning/thinking streams in the TUI across every provider, three new OpenAI-compatible backends (LM Studio, xAI, Kimi) join the auth/discovery/catalog surface, and Gemini gains native function-calling._
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Multi-provider reasoning/thinking streaming in the TUI.** Native reasoning is surfaced live (dimmed) and committed to scrollback for Anthropic (`thinking` deltas), OpenAI Codex/Responses (`reasoning*` deltas), OpenAI-compatible chat (`reasoning_content`/`reasoning`), Gemini & Antigravity (`thought` parts), and Ollama (`message.thinking`). A provider-agnostic `<think>…</think>` splitter routes inline chain-of-thought (DeepSeek-R1/Qwen-style local models) to the reasoning channel so it never pollutes the answer or the tool-call parse.
|
|
14
|
+
- **Three new OpenAI-compatible providers — LM Studio (keyless local), xAI/Grok (`XAI_API_KEY`), and Kimi/Moonshot (`KIMI_API_KEY`).** All route through a shared `makeOpenAICompatibleAdapter` factory and are wired into `/provider`, `jeo auth status/login`, model discovery, and the capability catalog.
|
|
15
|
+
- **Native Gemini function-calling (gjc parity).** Gemini now declares `functionDeclarations` and parses `functionCall` parts instead of the JSON-in-prose protocol — capable models stop fighting the `done` format, cutting wasted steps and stray "apology" prose from replies (verified live: a trivial reply dropped from 3 steps/14s to 1 step/2s).
|
|
16
|
+
- **Mid-turn `/command` and `$skill` dispatch** with a live command/skill preview while typing.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- **API-key providers are first-class in the auth core.** `AuthProvider` now splits into the OAuth-capable subset (`OAuthProvider`) plus API-key-only providers (xai/kimi); these resolve through the standard `resolveCredential` path (`config.providers` / `<NAME>_API_KEY`) and model discovery now sends their key (a prior gap left discovery unauthenticated). `jeo auth login <xai|kimi> --token <key>` stores the API key.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- **Config-schema dropped a stored `xai` key on validation** (the providers schema was missing `xai`/`kimi`); both are now persisted.
|
|
23
|
+
|
|
9
24
|
## [0.6.22] - 2026-06-18
|
|
10
25
|
_Extended-thinking activation is now consistent across providers: a `low` session thinking level enables reasoning everywhere._
|
|
11
26
|
|
package/README.ja.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
<img src="assets/hero.png" alt="jeo-code 自律コーディングエージェントのヒーローイラスト" width="100%" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
|
+
<p align="center">
|
|
6
|
+
<img src="assets/icon.png" alt="jeo-code icon" width="96" />
|
|
7
|
+
</p>
|
|
8
|
+
|
|
5
9
|
<h1 align="center">jeo-code (jeo)</h1>
|
|
6
10
|
|
|
7
11
|
<p align="center">
|
|
@@ -200,11 +204,11 @@ CI は `.github/workflows/npm-publish.yml` で公開します — GitHub リリ
|
|
|
200
204
|
## 変更履歴 (Changelog)
|
|
201
205
|
|
|
202
206
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
207
|
+
- **[0.6.23]** (2026-06-19) — Live reasoning/thinking streams in the TUI across every provider, three new OpenAI-compatible backends (LM Studio, xAI, Kimi) join the auth/discovery/catalog surface, and Gemini gains native function-calling.
|
|
203
208
|
- **[0.6.22]** (2026-06-18) — Extended-thinking activation is now consistent across providers: a `low` session thinking level enables reasoning everywhere.
|
|
204
209
|
- **[0.6.21]** (2026-06-18) — Session thinking level now reaches the provider's actual reasoning depth, not just the token ceiling.
|
|
205
210
|
- **[0.6.20]** (2026-06-18) — Launch REPL internals decomposed into testable modules: `@mention` path completion, slash-command view renderers, and slash-command handlers extracted from the monolithic `launch.ts` into dedicated files with full unit-test coverage.
|
|
206
211
|
- **[0.6.19]** (2026-06-18) — Post-turn hooks run once per batch (not per edit), local hook reads are mtime-cached, tool-result formatting is parallelized, and wrapped colored text keeps its tint.
|
|
207
|
-
- **[0.6.18]** (2026-06-17) — Memory data-flow diagram and a README "Memory flow" section documenting the actual runtime behavior.
|
|
208
212
|
|
|
209
213
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
210
214
|
<!-- CHANGELOG:END -->
|
package/README.ko.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
<img src="assets/hero.png" alt="jeo-code 자율 코딩 에이전트 히어로 일러스트" width="100%" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
|
+
<p align="center">
|
|
6
|
+
<img src="assets/icon.png" alt="jeo-code icon" width="96" />
|
|
7
|
+
</p>
|
|
8
|
+
|
|
5
9
|
<h1 align="center">jeo-code (jeo)</h1>
|
|
6
10
|
|
|
7
11
|
<p align="center">
|
|
@@ -200,11 +204,11 @@ CI는 `.github/workflows/npm-publish.yml`로 배포합니다 — GitHub 릴리
|
|
|
200
204
|
## 변경 이력 (Changelog)
|
|
201
205
|
|
|
202
206
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
207
|
+
- **[0.6.23]** (2026-06-19) — Live reasoning/thinking streams in the TUI across every provider, three new OpenAI-compatible backends (LM Studio, xAI, Kimi) join the auth/discovery/catalog surface, and Gemini gains native function-calling.
|
|
203
208
|
- **[0.6.22]** (2026-06-18) — Extended-thinking activation is now consistent across providers: a `low` session thinking level enables reasoning everywhere.
|
|
204
209
|
- **[0.6.21]** (2026-06-18) — Session thinking level now reaches the provider's actual reasoning depth, not just the token ceiling.
|
|
205
210
|
- **[0.6.20]** (2026-06-18) — Launch REPL internals decomposed into testable modules: `@mention` path completion, slash-command view renderers, and slash-command handlers extracted from the monolithic `launch.ts` into dedicated files with full unit-test coverage.
|
|
206
211
|
- **[0.6.19]** (2026-06-18) — Post-turn hooks run once per batch (not per edit), local hook reads are mtime-cached, tool-result formatting is parallelized, and wrapped colored text keeps its tint.
|
|
207
|
-
- **[0.6.18]** (2026-06-17) — Memory data-flow diagram and a README "Memory flow" section documenting the actual runtime behavior.
|
|
208
212
|
|
|
209
213
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
210
214
|
<!-- CHANGELOG:END -->
|
package/README.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
<img src="assets/hero.png" alt="jeo-code autonomous coding-agent hero illustration" width="100%" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
|
+
<p align="center">
|
|
6
|
+
<img src="assets/icon.png" alt="jeo-code icon" width="96" />
|
|
7
|
+
</p>
|
|
8
|
+
|
|
5
9
|
<h1 align="center">jeo-code (jeo)</h1>
|
|
6
10
|
|
|
7
11
|
<p align="center">
|
|
@@ -200,11 +204,11 @@ Required npm token permissions (repository secret `NPM_TOKEN`):
|
|
|
200
204
|
## Changelog
|
|
201
205
|
|
|
202
206
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
207
|
+
- **[0.6.23]** (2026-06-19) — Live reasoning/thinking streams in the TUI across every provider, three new OpenAI-compatible backends (LM Studio, xAI, Kimi) join the auth/discovery/catalog surface, and Gemini gains native function-calling.
|
|
203
208
|
- **[0.6.22]** (2026-06-18) — Extended-thinking activation is now consistent across providers: a `low` session thinking level enables reasoning everywhere.
|
|
204
209
|
- **[0.6.21]** (2026-06-18) — Session thinking level now reaches the provider's actual reasoning depth, not just the token ceiling.
|
|
205
210
|
- **[0.6.20]** (2026-06-18) — Launch REPL internals decomposed into testable modules: `@mention` path completion, slash-command view renderers, and slash-command handlers extracted from the monolithic `launch.ts` into dedicated files with full unit-test coverage.
|
|
206
211
|
- **[0.6.19]** (2026-06-18) — Post-turn hooks run once per batch (not per edit), local hook reads are mtime-cached, tool-result formatting is parallelized, and wrapped colored text keeps its tint.
|
|
207
|
-
- **[0.6.18]** (2026-06-17) — Memory data-flow diagram and a README "Memory flow" section documenting the actual runtime behavior.
|
|
208
212
|
|
|
209
213
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
210
214
|
<!-- CHANGELOG:END -->
|
package/README.zh.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
<img src="assets/hero.png" alt="jeo-code 自主编码代理主视觉插图" width="100%" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
|
+
<p align="center">
|
|
6
|
+
<img src="assets/icon.png" alt="jeo-code icon" width="96" />
|
|
7
|
+
</p>
|
|
8
|
+
|
|
5
9
|
<h1 align="center">jeo-code (jeo)</h1>
|
|
6
10
|
|
|
7
11
|
<p align="center">
|
|
@@ -200,11 +204,11 @@ CI 通过 `.github/workflows/npm-publish.yml` 发布 — GitHub 发布 release
|
|
|
200
204
|
## 更新日志 (Changelog)
|
|
201
205
|
|
|
202
206
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
207
|
+
- **[0.6.23]** (2026-06-19) — Live reasoning/thinking streams in the TUI across every provider, three new OpenAI-compatible backends (LM Studio, xAI, Kimi) join the auth/discovery/catalog surface, and Gemini gains native function-calling.
|
|
203
208
|
- **[0.6.22]** (2026-06-18) — Extended-thinking activation is now consistent across providers: a `low` session thinking level enables reasoning everywhere.
|
|
204
209
|
- **[0.6.21]** (2026-06-18) — Session thinking level now reaches the provider's actual reasoning depth, not just the token ceiling.
|
|
205
210
|
- **[0.6.20]** (2026-06-18) — Launch REPL internals decomposed into testable modules: `@mention` path completion, slash-command view renderers, and slash-command handlers extracted from the monolithic `launch.ts` into dedicated files with full unit-test coverage.
|
|
206
211
|
- **[0.6.19]** (2026-06-18) — Post-turn hooks run once per batch (not per edit), local hook reads are mtime-cached, tool-result formatting is parallelized, and wrapped colored text keeps its tint.
|
|
207
|
-
- **[0.6.18]** (2026-06-17) — Memory data-flow diagram and a README "Memory flow" section documenting the actual runtime behavior.
|
|
208
212
|
|
|
209
213
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
210
214
|
<!-- CHANGELOG:END -->
|
package/package.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { findCatalogEntry } from "../ai/model-catalog-compat";
|
|
2
|
+
import { OPENAI_COMPAT_PROVIDERS } from "../ai/providers/openai-compatible-catalog";
|
|
2
3
|
import { CODEX_MODELS } from "../ai/model-catalog";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
|
|
@@ -18,6 +19,11 @@ const StoredOAuthSchema = z.object({
|
|
|
18
19
|
});
|
|
19
20
|
|
|
20
21
|
const OAuthEntry = z.union([z.string(), StoredOAuthSchema]);
|
|
22
|
+
// Catalog-driven OpenAI-compatible providers contribute their own apiKey + oauth-slot
|
|
23
|
+
// schema keys (incl. hyphenated names like `alibaba-coding-plan`), so config-file keys
|
|
24
|
+
// are validated/kept rather than stripped. Adding a provider = one catalog row.
|
|
25
|
+
const compatKeySchema = Object.fromEntries(OPENAI_COMPAT_PROVIDERS.map(p => [p.name, z.string().optional()]));
|
|
26
|
+
const compatOAuthSchema = Object.fromEntries(OPENAI_COMPAT_PROVIDERS.map(p => [p.name, OAuthEntry.optional()]));
|
|
21
27
|
const HookConfigSchema = z.object({
|
|
22
28
|
enabled: z.boolean().optional(),
|
|
23
29
|
hooks: z
|
|
@@ -45,6 +51,9 @@ export const ConfigSchema = z
|
|
|
45
51
|
openai: z.string().optional(),
|
|
46
52
|
gemini: z.string().optional(),
|
|
47
53
|
antigravity: z.string().optional(),
|
|
54
|
+
xai: z.string().optional(),
|
|
55
|
+
kimi: z.string().optional(),
|
|
56
|
+
...compatKeySchema,
|
|
48
57
|
})
|
|
49
58
|
.default({}),
|
|
50
59
|
oauth: z
|
|
@@ -53,6 +62,9 @@ export const ConfigSchema = z
|
|
|
53
62
|
openai: OAuthEntry.optional(),
|
|
54
63
|
gemini: OAuthEntry.optional(),
|
|
55
64
|
antigravity: OAuthEntry.optional(),
|
|
65
|
+
xai: OAuthEntry.optional(),
|
|
66
|
+
kimi: OAuthEntry.optional(),
|
|
67
|
+
...compatOAuthSchema,
|
|
56
68
|
})
|
|
57
69
|
.optional(),
|
|
58
70
|
ollamaBaseUrl: z.string().optional(),
|
package/src/agent/session.ts
CHANGED
|
@@ -404,9 +404,16 @@ export async function exportSession(
|
|
|
404
404
|
const role = m.role.charAt(0).toUpperCase() + m.role.slice(1);
|
|
405
405
|
// Fence longer than the longest backtick run in the body (CommonMark) so message
|
|
406
406
|
// content containing ``` doesn't prematurely close the code fence.
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
407
|
+
const fenceFor = (s: string) => "`".repeat(Math.max(3, (s.match(/`+/g) ?? []).reduce((mx, r) => Math.max(mx, r.length), 0) + 1));
|
|
408
|
+
lines.push(`## ${role}`, "");
|
|
409
|
+
// Persisted thinking (gjc "think → answer"): include it in the durable export so the
|
|
410
|
+
// markdown record matches the in-app transcript and the JSON export.
|
|
411
|
+
if (m.role === "assistant" && m.reasoning?.trim()) {
|
|
412
|
+
const tf = fenceFor(m.reasoning);
|
|
413
|
+
lines.push("### Thinking", "", tf, m.reasoning, tf, "");
|
|
414
|
+
}
|
|
415
|
+
const fence = fenceFor(m.content);
|
|
416
|
+
lines.push(fence, m.content, fence, "");
|
|
410
417
|
}
|
|
411
418
|
return lines.join("\n");
|
|
412
419
|
}
|
package/src/agent/state.ts
CHANGED
|
@@ -2,6 +2,8 @@ import * as fs from "node:fs/promises";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import { parseConfig } from "./config-schema";
|
|
5
|
+
import type { AuthProvider } from "../auth/storage";
|
|
6
|
+
import { OPENAI_COMPAT_PROVIDERS } from "../ai/providers/openai-compatible-catalog";
|
|
5
7
|
import { jeoEnv } from "../util/env";
|
|
6
8
|
|
|
7
9
|
/** Persisted OAuth credential set (access + refresh + expiry) for a provider. */
|
|
@@ -26,27 +28,21 @@ export interface HookConfig {
|
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
export interface Config {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
openai?: string;
|
|
32
|
-
gemini?: string;
|
|
33
|
-
antigravity?: string;
|
|
34
|
-
};
|
|
31
|
+
/** Per-provider API keys, keyed by AuthProvider (cloud keys + catalog OpenAI-compatible). */
|
|
32
|
+
providers: Partial<Record<AuthProvider, string>>;
|
|
35
33
|
/**
|
|
36
34
|
* OAuth credentials. `resolveCredential()` returns these before API keys so refresh
|
|
37
35
|
* metadata is not lost, but provider execution/status applies the GJC parity rule:
|
|
38
|
-
* an API key is broader and wins whenever both key + OAuth exist.
|
|
36
|
+
* an API key is broader and wins whenever both key + OAuth exist. API-key-only
|
|
37
|
+
* providers never populate OAuth; the key exists for index-compatibility.
|
|
39
38
|
*/
|
|
40
|
-
oauth?:
|
|
41
|
-
anthropic?: string | StoredOAuth;
|
|
42
|
-
openai?: string | StoredOAuth;
|
|
43
|
-
gemini?: string | StoredOAuth;
|
|
44
|
-
antigravity?: string | StoredOAuth;
|
|
45
|
-
};
|
|
39
|
+
oauth?: Partial<Record<AuthProvider, string | StoredOAuth>>;
|
|
46
40
|
/** Base URL for the local Ollama server (keyless). */
|
|
47
41
|
ollamaBaseUrl?: string;
|
|
48
|
-
/** Base URL override for OpenAI-compatible providers (
|
|
42
|
+
/** Base URL override for OpenAI-compatible providers (vLLM, llama-cpp-server, ...). */
|
|
49
43
|
openaiBaseUrl?: string;
|
|
44
|
+
/** Base URL for the local LM Studio server (keyless, OpenAI-compatible). */
|
|
45
|
+
lmstudioBaseUrl?: string;
|
|
50
46
|
defaultModel: string;
|
|
51
47
|
theme?: string;
|
|
52
48
|
thinkingLevel?: "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
@@ -193,6 +189,13 @@ function withEnvOverlay(cfg: Config): Config {
|
|
|
193
189
|
if (!providers.anthropic && process.env.ANTHROPIC_API_KEY) providers.anthropic = process.env.ANTHROPIC_API_KEY;
|
|
194
190
|
if (!providers.openai && process.env.OPENAI_API_KEY) providers.openai = process.env.OPENAI_API_KEY;
|
|
195
191
|
if (!providers.gemini && process.env.GEMINI_API_KEY) providers.gemini = process.env.GEMINI_API_KEY;
|
|
192
|
+
if (!providers.xai && process.env.XAI_API_KEY) providers.xai = process.env.XAI_API_KEY;
|
|
193
|
+
// Catalog-driven OpenAI-compatible providers: each provider's own `apiKeyEnv`
|
|
194
|
+
// (e.g. GROQ_API_KEY, HF_TOKEN, NANO_GPT_API_KEY) fills config.providers[name].
|
|
195
|
+
for (const def of OPENAI_COMPAT_PROVIDERS) {
|
|
196
|
+
const key = def.name as AuthProvider; // every catalog name is an AuthProvider
|
|
197
|
+
if (!providers[key] && process.env[def.apiKeyEnv]) providers[key] = process.env[def.apiKeyEnv];
|
|
198
|
+
}
|
|
196
199
|
return {
|
|
197
200
|
...cfg,
|
|
198
201
|
providers,
|
|
@@ -200,6 +203,7 @@ function withEnvOverlay(cfg: Config): Config {
|
|
|
200
203
|
defaultModel: jeoEnv("DEFAULT_MODEL") || cfg.defaultModel,
|
|
201
204
|
ollamaBaseUrl: cfg.ollamaBaseUrl || process.env.OLLAMA_HOST || "http://localhost:11434",
|
|
202
205
|
openaiBaseUrl: cfg.openaiBaseUrl || process.env.OPENAI_BASE_URL,
|
|
206
|
+
lmstudioBaseUrl: cfg.lmstudioBaseUrl || process.env.LMSTUDIO_BASE_URL || "http://localhost:1234/v1",
|
|
203
207
|
roles: {
|
|
204
208
|
smol: cfg.roles?.smol || jeoEnv("SMOL_MODEL"),
|
|
205
209
|
slow: cfg.roles?.slow || jeoEnv("SLOW_MODEL"),
|
|
@@ -214,6 +218,7 @@ function envDefaultConfig(): Config {
|
|
|
214
218
|
anthropic: process.env.ANTHROPIC_API_KEY,
|
|
215
219
|
openai: process.env.OPENAI_API_KEY,
|
|
216
220
|
gemini: process.env.GEMINI_API_KEY,
|
|
221
|
+
xai: process.env.XAI_API_KEY,
|
|
217
222
|
},
|
|
218
223
|
defaultModel: jeoEnv("DEFAULT_MODEL") || DEFAULT_MODEL,
|
|
219
224
|
thinkingLevel: "medium",
|
package/src/ai/index.ts
CHANGED
|
@@ -10,3 +10,4 @@ export { openaiAdapter } from "./providers/openai";
|
|
|
10
10
|
export { geminiAdapter } from "./providers/gemini";
|
|
11
11
|
export { antigravityAdapter } from "./providers/antigravity";
|
|
12
12
|
export { ollamaAdapter } from "./providers/ollama";
|
|
13
|
+
export { lmstudioAdapter } from "./providers/lmstudio";
|
package/src/ai/model-catalog.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* catalog annotates known ids with capabilities.
|
|
8
8
|
*/
|
|
9
9
|
import type { ProviderName } from "./types";
|
|
10
|
+
import { openaiCompatDef } from "./providers/openai-compatible-catalog";
|
|
10
11
|
|
|
11
12
|
export type ThinkLevel = "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
12
13
|
|
|
@@ -36,6 +37,8 @@ const STD: ThinkLevel[] = ["minimal", "low", "medium", "high"];
|
|
|
36
37
|
export const ANTIGRAVITY_MODELS = [
|
|
37
38
|
"claude-opus-4-5-thinking",
|
|
38
39
|
"claude-opus-4-6-thinking",
|
|
40
|
+
"claude-opus-4-8",
|
|
41
|
+
"claude-opus-4-8-thinking",
|
|
39
42
|
"claude-sonnet-4-5",
|
|
40
43
|
"claude-sonnet-4-5-thinking",
|
|
41
44
|
"claude-sonnet-4-6",
|
|
@@ -59,6 +62,10 @@ export const MODEL_CATALOG: readonly CatalogModel[] = [
|
|
|
59
62
|
{ canonical: "claude-sonnet-4-5", provider: "anthropic", providerModel: "claude-sonnet-4-5-20250929", contextTokens: 200_000, maxOutputTokens: 64_000, thinking: FULL, images: true },
|
|
60
63
|
{ canonical: "claude-opus-4-1", provider: "anthropic", providerModel: "claude-opus-4-1-20250805", contextTokens: 200_000, maxOutputTokens: 32_000, thinking: FULL, images: true },
|
|
61
64
|
{ canonical: "claude-opus-4-5", provider: "anthropic", providerModel: "claude-opus-4-5-20251101", contextTokens: 200_000, maxOutputTokens: 64_000, thinking: FULL, images: true },
|
|
65
|
+
// NOTE: confirm exact dated provider ids when these ship publicly; the family
|
|
66
|
+
// heuristic in `catalogMetadata` keeps reasoning working even before that.
|
|
67
|
+
{ canonical: "claude-opus-4-6", provider: "anthropic", providerModel: "claude-opus-4-6", contextTokens: 200_000, maxOutputTokens: 64_000, thinking: FULL, images: true },
|
|
68
|
+
{ canonical: "claude-opus-4-8", provider: "anthropic", providerModel: "claude-opus-4-8", contextTokens: 200_000, maxOutputTokens: 64_000, thinking: FULL, images: true },
|
|
62
69
|
// OpenAI
|
|
63
70
|
{ canonical: "gpt-4o", provider: "openai", providerModel: "gpt-4o", contextTokens: 128_000, maxOutputTokens: 16_384, thinking: [], images: true },
|
|
64
71
|
{ canonical: "gpt-4o-mini", provider: "openai", providerModel: "gpt-4o-mini", contextTokens: 128_000, maxOutputTokens: 16_384, thinking: [], images: true },
|
|
@@ -68,6 +75,16 @@ export const MODEL_CATALOG: readonly CatalogModel[] = [
|
|
|
68
75
|
{ canonical: "o4-mini", provider: "openai", providerModel: "o4-mini", contextTokens: 200_000, maxOutputTokens: 100_000, thinking: STD, images: true },
|
|
69
76
|
{ canonical: "gpt-5.5", provider: "openai", providerModel: "gpt-5.5", contextTokens: 400_000, maxOutputTokens: 128_000, thinking: FULL, images: true },
|
|
70
77
|
{ canonical: "gpt-5.4", provider: "openai", providerModel: "gpt-5.4", contextTokens: 400_000, maxOutputTokens: 128_000, thinking: FULL, images: true },
|
|
78
|
+
// xAI (Grok) — OpenAI-compatible at https://api.x.ai/v1 (XAI_API_KEY)
|
|
79
|
+
{ canonical: "grok-4.3", provider: "xai", providerModel: "grok-4.3", contextTokens: 256_000, maxOutputTokens: 64_000, thinking: FULL, images: true },
|
|
80
|
+
{ canonical: "grok-4-fast-reasoning", provider: "xai", providerModel: "grok-4-fast-reasoning", contextTokens: 2_000_000, maxOutputTokens: 64_000, thinking: FULL, images: true },
|
|
81
|
+
{ canonical: "grok-4-fast-non-reasoning", provider: "xai", providerModel: "grok-4-fast-non-reasoning", contextTokens: 2_000_000, maxOutputTokens: 64_000, thinking: [], images: true },
|
|
82
|
+
{ canonical: "grok-code-fast-1", provider: "xai", providerModel: "grok-code-fast-1", contextTokens: 256_000, maxOutputTokens: 64_000, thinking: FULL, images: false },
|
|
83
|
+
// Kimi (Moonshot) — OpenAI-compatible at https://api.moonshot.ai/v1 (KIMI_API_KEY)
|
|
84
|
+
{ canonical: "kimi-k2-0711-preview", provider: "kimi", providerModel: "kimi-k2-0711-preview", contextTokens: 128_000, maxOutputTokens: 16_384, thinking: [], images: false },
|
|
85
|
+
{ canonical: "kimi-thinking-preview", provider: "kimi", providerModel: "kimi-thinking-preview", contextTokens: 128_000, maxOutputTokens: 32_000, thinking: FULL, images: true },
|
|
86
|
+
{ canonical: "kimi-latest", provider: "kimi", providerModel: "kimi-latest", contextTokens: 128_000, maxOutputTokens: 16_384, thinking: [], images: true },
|
|
87
|
+
{ canonical: "moonshot-v1-128k", provider: "kimi", providerModel: "moonshot-v1-128k", contextTokens: 128_000, maxOutputTokens: 16_384, thinking: [], images: false },
|
|
71
88
|
// Google
|
|
72
89
|
{ canonical: "gemini-1.5-pro", provider: "gemini", providerModel: "gemini-1.5-pro", contextTokens: 1_000_000, maxOutputTokens: 8_192, thinking: [], images: true },
|
|
73
90
|
{ canonical: "gemini-2.0-flash", provider: "gemini", providerModel: "gemini-2.0-flash", contextTokens: 1_000_000, maxOutputTokens: 8_192, thinking: [], images: true },
|
|
@@ -134,13 +151,111 @@ export function catalogByProvider(provider: ProviderName): CatalogModel[] {
|
|
|
134
151
|
return MODEL_CATALOG.filter(m => m.provider === provider);
|
|
135
152
|
}
|
|
136
153
|
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Heuristic capability inference for ids the static catalog does not list yet.
|
|
157
|
+
*
|
|
158
|
+
* New model revisions ship faster than this file is edited (e.g. a fresh
|
|
159
|
+
* `claude-opus-4-8` before its entry is added). Rather than treating every
|
|
160
|
+
* uncatalogued id as "no reasoning" — which silently hides the thinking TUI —
|
|
161
|
+
* we map the id to its model family and version and synthesize metadata so a
|
|
162
|
+
* brand-new revision behaves like its catalogued siblings (e.g. opus-4-6).
|
|
163
|
+
*
|
|
164
|
+
* Conservative by design: returns `undefined` for ids that do not match a known
|
|
165
|
+
* reasoning-capable family, so random/unknown ids stay "unknown caps".
|
|
166
|
+
*/
|
|
167
|
+
export function inferCatalogMetadata(modelId: string): CatalogModel | undefined {
|
|
168
|
+
const raw = modelId.trim();
|
|
169
|
+
if (!raw) return undefined;
|
|
170
|
+
const antigravity = /^antigravity\//i.test(raw);
|
|
171
|
+
const id = raw.replace(/^antigravity\//i, "").toLowerCase();
|
|
172
|
+
|
|
173
|
+
// Anthropic Claude: opus/sonnet/haiku. Major version >= 4 ships extended
|
|
174
|
+
// thinking (mirrors every catalogued claude-4-x entry); claude-3-x does not.
|
|
175
|
+
const claude = id.match(/^claude-(opus|sonnet|haiku)-(\d+)(?:[-.](\d+))?/);
|
|
176
|
+
if (claude) {
|
|
177
|
+
const major = Number(claude[2]);
|
|
178
|
+
const thinking = major >= 4 ? FULL : [];
|
|
179
|
+
return {
|
|
180
|
+
canonical: raw,
|
|
181
|
+
provider: antigravity ? "antigravity" : "anthropic",
|
|
182
|
+
providerModel: id,
|
|
183
|
+
contextTokens: 200_000,
|
|
184
|
+
maxOutputTokens: claude[1] === "haiku" ? 64_000 : 64_000,
|
|
185
|
+
thinking,
|
|
186
|
+
images: true,
|
|
187
|
+
company: antigravity ? "Anthropic via Antigravity" : "Anthropic",
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// OpenAI reasoning families: the o-series (o1, o3, … any major incl. o10+) and
|
|
192
|
+
// gpt-5+ (digit-count agnostic so gpt-6/o10 never silently lose reasoning the way
|
|
193
|
+
// opus-4-8 did). gpt-4 and earlier are non-reasoning. Mirrors the openai.ts gate.
|
|
194
|
+
const gptMajor = id.match(/^gpt-(\d+)/);
|
|
195
|
+
const openaiReasoner = /^o\d+(-|$)/.test(id) || (gptMajor ? Number(gptMajor[1]) >= 5 : false);
|
|
196
|
+
if (openaiReasoner) {
|
|
197
|
+
const wide = gptMajor ? Number(gptMajor[1]) >= 5 : false;
|
|
198
|
+
return {
|
|
199
|
+
canonical: raw,
|
|
200
|
+
provider: antigravity ? "antigravity" : "openai",
|
|
201
|
+
providerModel: id,
|
|
202
|
+
contextTokens: wide ? 400_000 : 200_000,
|
|
203
|
+
maxOutputTokens: wide ? 128_000 : 100_000,
|
|
204
|
+
thinking: wide ? FULL : STD,
|
|
205
|
+
images: !id.includes("mini") || id.includes("o4-mini") || id.includes("o3"),
|
|
206
|
+
company: antigravity ? "OpenAI via Antigravity" : "OpenAI",
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Google Gemini: 2.5+ and 3.x expose thinking; 1.5/2.0 do not.
|
|
211
|
+
const gemini = id.match(/^gemini-(\d+)(?:\.(\d+))?/);
|
|
212
|
+
if (gemini) {
|
|
213
|
+
const major = Number(gemini[1]);
|
|
214
|
+
const minor = Number(gemini[2] ?? 0);
|
|
215
|
+
const reasons = major >= 3 || (major === 2 && minor >= 5);
|
|
216
|
+
const big3 = major >= 3;
|
|
217
|
+
return {
|
|
218
|
+
canonical: raw,
|
|
219
|
+
provider: antigravity ? "antigravity" : "gemini",
|
|
220
|
+
providerModel: id,
|
|
221
|
+
contextTokens: 1_000_000,
|
|
222
|
+
maxOutputTokens: 65_536,
|
|
223
|
+
thinking: !reasons ? [] : big3 || id.includes("thinking") || id.includes("-high") || id.includes("-low") ? FULL : STD,
|
|
224
|
+
images: true,
|
|
225
|
+
company: antigravity ? "Google Antigravity" : "Google",
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// xAI Grok 4+ reasoning variants.
|
|
230
|
+
const grok = id.match(/^grok-(\d+)/);
|
|
231
|
+
if (grok && Number(grok[1]) >= 4) {
|
|
232
|
+
const nonReasoning = id.includes("non-reasoning");
|
|
233
|
+
return {
|
|
234
|
+
canonical: raw,
|
|
235
|
+
provider: "xai",
|
|
236
|
+
providerModel: id,
|
|
237
|
+
contextTokens: id.includes("fast") ? 2_000_000 : 256_000,
|
|
238
|
+
maxOutputTokens: 64_000,
|
|
239
|
+
thinking: nonReasoning ? [] : FULL,
|
|
240
|
+
images: !id.includes("code"),
|
|
241
|
+
company: "xAI",
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
|
|
137
248
|
/** Annotate a discovered/raw model id with catalog metadata, when known. */
|
|
138
249
|
export function catalogMetadata(modelId: string): CatalogModel | undefined {
|
|
139
250
|
const direct = findCatalogModel(modelId);
|
|
140
251
|
if (direct) return direct;
|
|
141
252
|
// Tolerate provider-prefixed or bare provider model ids.
|
|
142
253
|
const bare = modelId.replace(/^[a-z-]+\//, "");
|
|
143
|
-
|
|
254
|
+
const hit = MODEL_CATALOG.find(m => m.providerModel === bare || m.providerModel.endsWith(`/${bare}`) || m.canonical === bare);
|
|
255
|
+
if (hit) return hit;
|
|
256
|
+
// Last resort: infer capabilities from the model family so a brand-new
|
|
257
|
+
// revision still surfaces reasoning/thinking like its catalogued siblings.
|
|
258
|
+
return inferCatalogMetadata(modelId);
|
|
144
259
|
}
|
|
145
260
|
|
|
146
261
|
/** Whether a model supports a given thinking level (per the catalog). */
|
|
@@ -157,6 +272,11 @@ export function companyLabel(provider: string, entry?: { company?: string }): st
|
|
|
157
272
|
if (low === "openai") return "OpenAI";
|
|
158
273
|
if (low === "gemini") return "Google";
|
|
159
274
|
if (low === "ollama") return "Ollama";
|
|
275
|
+
if (low === "lmstudio") return "LM Studio";
|
|
276
|
+
if (low === "xai") return "xAI";
|
|
277
|
+
if (low === "kimi") return "Moonshot";
|
|
278
|
+
const compat = openaiCompatDef(low);
|
|
279
|
+
if (compat) return compat.label;
|
|
160
280
|
if (low === "antigravity") return "Antigravity";
|
|
161
281
|
return provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
162
282
|
}
|
|
@@ -13,6 +13,7 @@ import type { ProviderName } from "./types";
|
|
|
13
13
|
import { PROVIDER_NAMES } from "./provider-status";
|
|
14
14
|
import { catalogByProvider, CODEX_MODELS } from "./model-catalog";
|
|
15
15
|
import { extractChatgptAccountId } from "./providers/openai-responses";
|
|
16
|
+
import { openaiCompatDef } from "./providers/openai-compatible-catalog";
|
|
16
17
|
|
|
17
18
|
export interface ProviderModelsResult {
|
|
18
19
|
provider: ProviderName;
|
|
@@ -68,7 +69,9 @@ function anthropicHeaders(cred: Credential): Record<string, string> {
|
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
function authProviderFor(provider: ProviderName): AuthProvider | undefined {
|
|
71
|
-
|
|
72
|
+
// Local providers (ollama/lmstudio) are keyless and do not resolve through the
|
|
73
|
+
// auth core. API-key providers (incl. xai/kimi) DO — so discovery sends their key.
|
|
74
|
+
if (provider === "ollama" || provider === "lmstudio") return undefined;
|
|
72
75
|
return provider;
|
|
73
76
|
}
|
|
74
77
|
|
|
@@ -78,6 +81,17 @@ export function discoveryRequest(
|
|
|
78
81
|
cred: Credential | undefined,
|
|
79
82
|
baseUrl?: string,
|
|
80
83
|
): { url: string; headers: Record<string, string>; method?: "GET" | "POST"; body?: string } {
|
|
84
|
+
// Catalog-driven compat providers: OpenAI `${base}/models` (Bearer) or Anthropic
|
|
85
|
+
// `${base}/v1/models` (x-api-key). Both return { data: [{ id }] }.
|
|
86
|
+
const compat = openaiCompatDef(provider);
|
|
87
|
+
if (compat) {
|
|
88
|
+
const base = (baseUrl ?? compat.baseUrl).replace(/\/$/, "");
|
|
89
|
+
const token = cred?.kind === "api_key" || cred?.kind === "oauth" ? cred.token : "";
|
|
90
|
+
if (compat.protocol === "anthropic") {
|
|
91
|
+
return { url: `${base}/v1/models`, headers: token ? { "x-api-key": token, "anthropic-version": "2023-06-01" } : {} };
|
|
92
|
+
}
|
|
93
|
+
return { url: `${base}/models`, headers: token ? { Authorization: `Bearer ${token}` } : {} };
|
|
94
|
+
}
|
|
81
95
|
switch (provider) {
|
|
82
96
|
case "anthropic":
|
|
83
97
|
return { url: "https://api.anthropic.com/v1/models", headers: anthropicHeaders(cred!) };
|
|
@@ -120,7 +134,21 @@ export function discoveryRequest(
|
|
|
120
134
|
const base = (baseUrl ?? "http://localhost:11434").replace(/\/$/, "");
|
|
121
135
|
return { url: `${base}/api/tags`, headers: {} };
|
|
122
136
|
}
|
|
137
|
+
case "lmstudio": {
|
|
138
|
+
const base = (baseUrl ?? "http://localhost:1234/v1").replace(/\/$/, "");
|
|
139
|
+
return { url: `${base}/models`, headers: {} };
|
|
140
|
+
}
|
|
141
|
+
case "xai": {
|
|
142
|
+
const token = cred?.kind === "api_key" ? cred.token : "";
|
|
143
|
+
return { url: "https://api.x.ai/v1/models", headers: token ? { Authorization: `Bearer ${token}` } : {} };
|
|
144
|
+
}
|
|
145
|
+
case "kimi": {
|
|
146
|
+
const token = cred?.kind === "api_key" ? cred.token : "";
|
|
147
|
+
return { url: "https://api.moonshot.ai/v1/models", headers: token ? { Authorization: `Bearer ${token}` } : {} };
|
|
148
|
+
}
|
|
123
149
|
}
|
|
150
|
+
// Unreachable: every ProviderName is a switch case or catalog-handled above.
|
|
151
|
+
throw new Error(`discoveryRequest: unhandled provider '${provider}'`);
|
|
124
152
|
}
|
|
125
153
|
|
|
126
154
|
/**
|
|
@@ -149,9 +177,26 @@ export function parseModelsBody(provider: ProviderName, body: unknown): string[]
|
|
|
149
177
|
data?: { id?: string }[];
|
|
150
178
|
models?: ({ name?: string; supportedGenerationMethods?: string[] } & CodexModelRow)[];
|
|
151
179
|
};
|
|
180
|
+
if (openaiCompatDef(provider)) {
|
|
181
|
+
// Catalog OpenAI-compatible: { data: [{ id }] }. Prefix-qualify so the router
|
|
182
|
+
// maps the id back to this provider (the ids alone don't heuristically route).
|
|
183
|
+
return (data.data ?? []).map(m => (m.id ? `${provider}/${m.id}` : "")).filter(Boolean);
|
|
184
|
+
}
|
|
152
185
|
if (provider === "ollama") {
|
|
153
186
|
return (data.models ?? []).map(m => `ollama/${m.name ?? ""}`).filter(s => s !== "ollama/");
|
|
154
187
|
}
|
|
188
|
+
if (provider === "lmstudio") {
|
|
189
|
+
// LM Studio is OpenAI-compatible: { data: [{ id }] }. Qualify with the routing prefix.
|
|
190
|
+
return (data.data ?? []).map(m => `lmstudio/${m.id ?? ""}`).filter(s => s !== "lmstudio/");
|
|
191
|
+
}
|
|
192
|
+
if (provider === "xai") {
|
|
193
|
+
// xAI is OpenAI-compatible: { data: [{ id }] }. Grok ids route to xai by name, so no prefix.
|
|
194
|
+
return (data.data ?? []).map(m => m.id ?? "").filter(Boolean);
|
|
195
|
+
}
|
|
196
|
+
if (provider === "kimi") {
|
|
197
|
+
// Moonshot is OpenAI-compatible: { data: [{ id }] }. kimi/moonshot ids route by name.
|
|
198
|
+
return (data.data ?? []).map(m => m.id ?? "").filter(Boolean);
|
|
199
|
+
}
|
|
155
200
|
if (provider === "antigravity") {
|
|
156
201
|
// fetchAvailableModels keys the map by the CALLABLE model id (e.g.
|
|
157
202
|
// "gemini-3-flash"); the entry's `model` field is an internal enum
|
|
@@ -252,7 +297,14 @@ export async function listProviderModels(
|
|
|
252
297
|
|
|
253
298
|
let cred: Credential | undefined;
|
|
254
299
|
let source: ProviderModelsResult["source"] = "keyless";
|
|
255
|
-
if (provider
|
|
300
|
+
if (provider === "xai") {
|
|
301
|
+
// xAI (Grok) is API-key only and not an OAuth AuthProvider: resolve its key
|
|
302
|
+
// directly from config/env instead of the AuthProvider credential store.
|
|
303
|
+
const key = (opts.config ?? (await readGlobalConfig())).providers?.xai;
|
|
304
|
+
if (!key) return { provider, models: [], ok: false, source: "none", error: "not logged in" };
|
|
305
|
+
cred = { kind: "api_key", provider: "openai", token: key };
|
|
306
|
+
source = "api_key";
|
|
307
|
+
} else if (provider !== "ollama" && provider !== "lmstudio") {
|
|
256
308
|
const authProvider = authProviderFor(provider);
|
|
257
309
|
const raw = await resolveCredential(authProvider!);
|
|
258
310
|
cred = raw;
|
|
@@ -338,7 +390,7 @@ export async function discoverModels(
|
|
|
338
390
|
listProviderModels(p, {
|
|
339
391
|
...opts,
|
|
340
392
|
config: cfg,
|
|
341
|
-
baseUrl: p === "ollama" ? (cfg.ollamaBaseUrl ?? opts.baseUrl) : p === "openai" ? (cfg.openaiBaseUrl ?? opts.baseUrl) : opts.baseUrl,
|
|
393
|
+
baseUrl: p === "ollama" ? (cfg.ollamaBaseUrl ?? opts.baseUrl) : p === "lmstudio" ? (cfg.lmstudioBaseUrl ?? opts.baseUrl) : p === "openai" ? (cfg.openaiBaseUrl ?? opts.baseUrl) : opts.baseUrl,
|
|
342
394
|
}),
|
|
343
395
|
),
|
|
344
396
|
);
|