mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-10 01:39:25 +00:00
* feat: add /connect command with models.dev catalog support Add a /connect slash command that configures a provider and model from a models.dev-style catalog. Users no longer need to hand-write model metadata (context window, output limit, capabilities). Architecture (3 layers): - kosong: pure data layer — Catalog schema, inferWireType, catalogModelToCapability - node-sdk: IO + config write — fetchCatalog, applyCatalogProvider, catalogModelToAlias - app: TUI flow — /connect command, provider/model selection, credential input, config persistence UI improvements in this PR: - ChoicePickerComponent: add searchable (fuzzy filter + search bar) - ModelSelectorComponent: add searchable (same) - Extract reusable paging.ts for list pagination Changesets included for kosong, kimi-code-sdk, and kimi-code. * feat: bundle pruned models.dev catalog for offline /connect When the network is unavailable, /connect now falls back to a built-in snapshot of the models.dev catalog. - `scripts/update-catalog.mjs`: fetches models.dev/api.json, strips unnecessary fields, and writes `src/built-in-catalog.ts` with the JSON string as a TS constant. - `loadBuiltInCatalog(text?)` in node-sdk: parses the JSON string safely; returns undefined on any failure. - `handleConnectCommand`: on fetch failure, shows an informative offline message and tries the built-in snapshot. - The snapshot file is a placeholder (`undefined`) in source control; `update-catalog.mjs` populates it before release builds so the actual catalog is inlined into the bundle by rolldown. * refactor(tui): share search and pagination across list pickers ChoicePicker and ModelSelector each carried their own copy of the cursor + fuzzy-search + pagination state machine. Extract it into a reusable SearchableList so both pickers share one implementation; behavior is unchanged. * fix(tui): filter unsupported catalog providers * docs(tui): clarify /connect stale-alias cleanup depends on removeProvider * fix(kimi-code): inject built-in catalog at release time * feat(tui): hint at /login and /connect when /model has no models Replace the bare "No models configured." error with a notice that points users to /login for Kimi and /connect for other providers. * fix(tui): tighten /connect error reporting for edge cases Two silent-failure cases in /connect could leave users without any feedback to act on: - Reject `--url` when its value is missing (e.g. `/connect --url` or `/connect --url=`). Previously the argument parser silently fell back to the default catalog, so a malformed flag still appeared to succeed but with the wrong source. - Show an explicit error when the resolved catalog yields no providers with supported wire types. Previously the picker resolved with no selection and the command returned without any UI feedback. * chore(tui): restore slash invalid intent type * fix(tui): support /logout for /connect-configured providers After /connect writes a non-managed provider (e.g. openai), /logout fell through to "Nothing to logout." because the handler only matched the managed default and isOpenPlatformId branches, leaving users no in-app way to drop the API key and model aliases they just configured. Collapse the OpenPlatform branch into a generic "provider is present in config" check so any non-managed provider in config — OpenPlatform OAuth targets and /connect catalog providers — goes through the same removeProvider path. * fix(tui): reject --url values that are not http(s) URLs `resolveConnectCatalogRequest` previously matched any non-space token after `--url` as the URL, so `/connect --url --refresh` parsed `--refresh` as the value and bypassed the missing-value error path. Bare non-URL tokens (`/connect --url not-a-url`) and non-http(s) schemes were also silently accepted. Constrain the captured value to `https?://...` so flag-like and non-URL tokens fall through to the existing `URL_FLAG_PRESENT_RE` check and surface a clear error. * ci(native): scope built-in catalog generation to signed macOS jobs The catalog-generation step ran whenever `inputs.sign-macos` was true, including Linux and Windows targets that take the local-profile build path and never consume the generated catalog. A transient models.dev outage would therefore fail unrelated artifact builds. Match the condition to the macOS signed release-profile build that actually consumes the bundled catalog. * fix(ci): embed built-in catalog in non-macOS native artifacts The earlier narrowing to `runner.os == 'macOS' && inputs.sign-macos` relied on a misread of build.mjs: its `profile === 'release'` guard only auto-fetches the catalog as a dev fallback. Whether the binary actually embeds the catalog is decided by tsdown's define at bundle time, which reads KIMI_CODE_BUILT_IN_CATALOG_FILE regardless of profile. Linux and Windows release artifacts therefore lost their bundled catalog and silently regressed offline /connect on those targets. Restore generation for all OS jobs when sign-macos is true. * chore(tui): mention /connect in welcome panel hints Align the welcome panel with the /model picker so the empty-state copy points users to both /login and /connect.
87 lines
2.8 KiB
JavaScript
87 lines
2.8 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Fetches models.dev/api.json, strips fields not needed by kimi-code, and
|
|
* writes the result as raw JSON for release builds to inline.
|
|
*
|
|
* This script intentionally does not write into src/. The source tree keeps a
|
|
* placeholder so the generated catalog is not committed.
|
|
*/
|
|
|
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
import { dirname, resolve } from "node:path";
|
|
|
|
const scriptDir = import.meta.dirname;
|
|
const outFile = resolveOutputFile(process.argv.slice(2));
|
|
const modelsUrl = process.env.MODELS_DEV_URL || "https://models.dev/api.json";
|
|
|
|
const KEEP_PROVIDER = new Set(["id", "name", "api", "env", "npm", "type", "models"]);
|
|
const KEEP_MODEL = new Set(["id", "name", "family", "limit", "tool_call", "reasoning", "modalities"]);
|
|
|
|
function resolveOutputFile(args) {
|
|
const index = args.indexOf("--out");
|
|
if (index !== -1) {
|
|
const value = args[index + 1];
|
|
if (value === undefined || value.length === 0) {
|
|
throw new Error("Missing value for --out");
|
|
}
|
|
return resolve(process.cwd(), value);
|
|
}
|
|
return resolve(scriptDir, "../dist/built-in-catalog.json");
|
|
}
|
|
|
|
function stripModel(model) {
|
|
if (typeof model !== "object" || model === null) return undefined;
|
|
const result = {};
|
|
for (const key of Object.keys(model)) {
|
|
if (KEEP_MODEL.has(key)) result[key] = model[key];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function stripProvider(provider) {
|
|
if (typeof provider !== "object" || provider === null) return undefined;
|
|
const result = {};
|
|
for (const key of Object.keys(provider)) {
|
|
if (!KEEP_PROVIDER.has(key)) continue;
|
|
const value = provider[key];
|
|
if (key === "models") {
|
|
const stripped = {};
|
|
for (const [mId, m] of Object.entries(value)) {
|
|
const s = stripModel(m);
|
|
if (s !== undefined) stripped[mId] = s;
|
|
}
|
|
if (Object.keys(stripped).length > 0) result[key] = stripped;
|
|
} else {
|
|
result[key] = value;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
async function fetchCatalog(url) {
|
|
const res = await fetch(url, { headers: { Accept: "application/json" } });
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
const raw = await res.json();
|
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
throw new Error("invalid payload shape");
|
|
}
|
|
const stripped = {};
|
|
for (const [k, v] of Object.entries(raw)) {
|
|
const p = stripProvider(v);
|
|
if (p !== undefined && Object.keys(p).length > 0) stripped[k] = p;
|
|
}
|
|
return JSON.stringify(stripped);
|
|
}
|
|
|
|
async function main() {
|
|
console.log(`Fetching ${modelsUrl} ...`);
|
|
const json = await fetchCatalog(modelsUrl);
|
|
mkdirSync(dirname(outFile), { recursive: true });
|
|
writeFileSync(outFile, json, "utf-8");
|
|
console.log(`Wrote ${outFile} (${(json.length / 1024).toFixed(0)} KB JSON)`);
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error(error.message);
|
|
process.exit(1);
|
|
});
|