feat(kimi-code): add /connect command with bundled model catalog#30
feat(kimi-code): add /connect command with bundled model catalog#307Sageer wants to merge 14 commits into
Conversation
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.
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.
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.
Replace the bare "No models configured." error with a notice that points users to /login for Kimi and /connect for other providers.
🦋 Changeset detectedLatest commit: f037ea6 The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a50f2ae029
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (options.length === 0) { | ||
| resolve(undefined); | ||
| return; |
There was a problem hiding this comment.
Report unsupported catalogs instead of silently returning
If a custom catalog only contains providers that inferWireType cannot map, options becomes empty and this function resolves undefined, after which handleConnectCommand exits immediately. In that case /connect --url=... appears to do nothing (no picker, no error, no hint), which makes troubleshooting impossible for users of private or partially compatible catalogs; showing an explicit error/status here would make the failure actionable.
Useful? React with 👍 / 👎.
| const refreshRequested = REFRESH_FLAG_RE.test(trimmed); | ||
| return { | ||
| url: DEFAULT_CATALOG_URL, | ||
| preferBuiltIn: !refreshRequested, | ||
| allowBuiltInFallback: true, |
There was a problem hiding this comment.
Reject
--url when no value is provided
When users run /connect --url or /connect --url=, the URL regex does not match, so explicitUrl stays undefined and the parser silently falls back to the default catalog path. This can lead to configuring a provider from the built-in/default catalog even though the user intended a custom source, and there is no error indicating the argument was malformed.
Useful? React with 👍 / 👎.
…catalog # Conflicts: # apps/kimi-code/test/tui/kimi-tui-message-flow.test.ts
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.
There was a problem hiding this comment.
💡 Codex Review
kimi-code/apps/kimi-code/src/tui/kimi-tui.ts
Lines 5285 to 5287 in b32ed1b
After /connect writes a non-managed provider (for example openai), /logout falls through to "Nothing to logout." because it only handles managed:kimi-code and isOpenPlatformId(...). This leaves users unable to clear API keys/providers they just configured through the new flow, which is a functional regression introduced by making custom providers first-class in the UI.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| @@ -0,0 +1,51 @@ | |||
| import { DEFAULT_CATALOG_URL } from '@moonshot-ai/kimi-code-sdk'; | |||
|
|
|||
| const CATALOG_URL_FLAG_RE = /--url(?:=|\s+)(\S+)/; | |||
There was a problem hiding this comment.
Reject flag tokens consumed as --url values
resolveConnectCatalogRequest treats any non-space token after --url as a URL, so /connect --url --refresh parses --refresh as request.url and bypasses the missing-value error path. That leads to an attempted fetch of an invalid URL and disables built-in fallback (allowBuiltInFallback: false), producing a confusing failure instead of the intended “--url requires a value” validation.
Useful? React with 👍 / 👎.
| if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) { | ||
| throw new Error(`Unexpected catalog response from ${url}.`); | ||
| } | ||
| return payload as Catalog; |
There was a problem hiding this comment.
Validate catalog entry shapes before returning payload
fetchCatalog only checks that the top-level JSON is an object, then casts it to Catalog without validating provider entry values. For custom catalogs (e.g. /connect --url=...) containing null or non-object provider values, downstream calls like inferWireType(entry) will throw on property access, surfacing an internal runtime error instead of a catalog-format error; this should be rejected at fetch/parse time.
Useful? React with 👍 / 👎.
| certificate-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | ||
|
|
||
| - name: Generate built-in catalog (release artifacts) | ||
| if: inputs.sign-macos |
There was a problem hiding this comment.
Scope catalog generation to signed macOS release jobs
In the _native-build.yml matrix, the catalog-generation step runs whenever inputs.sign-macos is true, including Linux and Windows targets that later take the local-profile build path. This injects an unnecessary external fetch dependency into those jobs, so a transient models.dev failure can fail non-macOS artifact builds that do not consume the generated catalog.
Useful? React with 👍 / 👎.
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.
`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.
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.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bad6c803c0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| # path consumes the bundled catalog. Running this on Linux/Windows | ||
| # would make their builds fail on transient models.dev outages | ||
| # without providing any benefit. | ||
| if: runner.os == 'macOS' && inputs.sign-macos |
There was a problem hiding this comment.
Generate built-in catalog for all released native targets
The new catalog step is gated to runner.os == 'macOS' && inputs.sign-macos, but this workflow still publishes Linux/Windows artifacts built through the local-profile path (build:native:sea), and apps/kimi-code/scripts/native/build.mjs only auto-generates the catalog for profile === 'release'. As a result, non-macOS release binaries are built without KIMI_CODE_BUILT_IN_CATALOG_FILE, leaving BUILT_IN_CATALOG_JSON undefined at runtime and breaking the intended offline-by-default /connect behavior on those targets.
Useful? React with 👍 / 👎.
| - name: Generate Kimi Code built-in catalog | ||
| shell: bash | ||
| run: | | ||
| CATALOG_FILE="$RUNNER_TEMP/kimi-code-built-in-catalog.json" | ||
| node apps/kimi-code/scripts/update-catalog.mjs --out "$CATALOG_FILE" | ||
| echo "KIMI_CODE_BUILT_IN_CATALOG_FILE=$CATALOG_FILE" >> "$GITHUB_ENV" |
There was a problem hiding this comment.
Avoid blocking all releases on catalog fetch availability
The release workflow now unconditionally fetches models.dev before any package build/publish step, so a transient outage or network failure at that endpoint fails the entire monorepo release job (including unrelated packages). Because the catalog is only needed for embedding into Kimi Code artifacts, this new always-on external dependency can halt normal release operations even when no Kimi Code package is being shipped.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
By design — /connect offline-by-default is a release-time commitment. If the catalog can't be fetched, the produced binaries would silently lose offline behavior, which is a degraded release and should block. The right response to a transient models.dev outage here is to retry the release, not decouple the dependency. Coupling unrelated packages (kosong, agent-core, etc.) to catalog availability is the cost of a single release pipeline; we prefer that over silently shipping degraded kimi-code.
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.
Summary
/connectcommand that configures a provider + model from a models.dev-compatible catalog, with model metadata (context window, output limit, capabilities) filled in automatically./connectworks offline by default and is not gated by models.dev availability;--refreshopts in to fetching the latest catalog from models.dev (falling back to the bundled snapshot on failure)./login(Kimi) and/connect(other providers) when/modelis opened with no configured models.Test plan
pnpm typecheckcleanpnpm test— 850 passedpnpm lint— 0 warnings/connectloads the bundled catalog with no network/connect --refreshfetches from models.dev/connect --url=<custom>honors explicit URLs and surfaces fetch errors/modelwith no models shows the new hint