diff --git a/AGENTS.md b/AGENTS.md index 6686052..5c26c32 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -30,11 +30,12 @@ Current honest state: installer runtime - `src/install/` contains the runtime contracts, dependency adapters, orchestration, managed writes, rollback, redaction, and verification helpers -- `moonshotai/kimi-k2.6` is MiMoCode-validated for the current - `@mimo-ai/cli` `0.1.0` baseline and is the recommended public default -- `minimaxai/minimax-m2.7` and - `qwen/qwen3-235b-a22b-instruct-2507-fp8` remain MiMoCode candidates, not - public validated models +- the runtime collects the GonkaGate key before the model picker, calls + `GET https://api.gonkagate.com/v1/models`, and writes every returned model + into `provider.gonkagate.models` +- `moonshotai/kimi-k2.6` has MiMoCode validation proof for the current + `@mimo-ai/cli` `0.1.0` baseline, but the public picker is now backed by the + live GonkaGate model catalog rather than a hardcoded validated allowlist If implementation status, package name, security flow, config locations, transport contract, or verified MiMoCode baseline changes, this file must be @@ -46,10 +47,11 @@ The intended happy path is: 1. user runs `npx @gonkagate/mimo-code-setup` 2. installer validates local `mimo` -3. installer offers only MiMoCode-validated GonkaGate models -4. installer asks for `user` or `project` scope -5. installer collects a GonkaGate `gp-...` key through a hidden prompt, +3. installer collects a GonkaGate `gp-...` key through a hidden prompt, `GONKAGATE_API_KEY`, or `--api-key-stdin` +4. installer calls `GET /v1/models` with that key and offers all returned + GonkaGate models +5. installer asks for `user` or `project` scope 6. installer writes the minimum safe MiMoCode config layers 7. installer verifies durable MiMoCode config and current-session effective config @@ -68,6 +70,7 @@ change. - intended public npm entrypoint: `npx @gonkagate/mimo-code-setup` - stable provider id: `gonkagate` - canonical base URL: `https://api.gonkagate.com/v1` +- runtime model catalog source: `GET https://api.gonkagate.com/v1/models` - current transport target: `chat_completions` - current provider package: `@ai-sdk/openai-compatible` - future `/v1/responses` support should be added by migration, not product @@ -90,6 +93,8 @@ change. - canonical installer-owned cache-key setting: `provider.gonkagate.options.setCacheKey = false` - project config must not own the secret binding +- public setup must fetch the model catalog after safe API-key intake instead + of exposing a hardcoded model allowlist - installer success must be based on effective MiMoCode config, not only file writes - raw `mimo --pure debug config` output must not be printed because `{file:...}` @@ -120,11 +125,11 @@ change. - `docs/specs/mimo-code-setup-prd/spec.md` is the product source of truth - `src/cli.ts` is the public runtime entrypoint and calls `src/install/` through `src/cli/` parse/execute/render seams -- `src/install/` contains successful setup orchestration for the validated - public registry and preserves the blocked path for custom candidate-only - registries +- `src/install/` contains successful setup orchestration for safe secret + intake, live GonkaGate model-catalog fetch, dynamic provider catalog writes, + and effective-config verification - `src/constants/` pins package, provider, transport, config, and - model-registry contracts + model-validation metadata contracts - `bin/gonkagate-mimo-code.js` is a thin wrapper over `dist/cli.js` - `.github/workflows/` contains CI, release-please, and npm publish workflows - `.agents/skills/` and `.claude/skills/` contain mirrored local skill packs @@ -137,14 +142,14 @@ This repo currently does: - define the product contract for the MiMoCode setup tool - provide npm packaging, CI, release-please, and publish scaffolding - provide a public CLI entrypoint that can configure MiMoCode when the local - `mimo` baseline, secret input, and effective-config verification pass + `mimo` baseline, secret input, live model-catalog fetch, and + effective-config verification pass - provide docs and tests that protect the current runtime contract - provide mirrored local skills for repo-aware agent work -- expose `moonshotai/kimi-k2.6` as the current MiMoCode-validated public model +- expose every model returned by GonkaGate `GET /v1/models` as a setup choice This repo currently does not do: -- expose candidate-only GonkaGate models as public setup choices - write direct MiMoCode `auth.json` - mutate shell profiles - generate `.env` files @@ -200,12 +205,17 @@ Current public entrypoint and split parse/execute/render seams. Runtime implementation for installer contracts, dependency injection, platform/path helpers, managed writes, rollback, verification, and redacted -results. The default public registry currently exposes `moonshotai/kimi-k2.6` -after MiMoCode validation proof. +results. The default public flow fetches the GonkaGate model catalog from +`/v1/models` after safe API-key intake. + +### `src/install/model-catalog.ts` + +Trust-boundary parser and fetch adapter for `GET /v1/models`. Keep it strict +about response shape and redaction-safe about failures. ### `src/constants/` -Package, provider, transport, path, and model-registry constants. +Package, provider, transport, path, and model-validation constants. ### `.agents/skills/` and `.claude/skills/` @@ -225,14 +235,14 @@ When behavior changes: - keep scaffold docs and future runtime docs explicitly labeled so they cannot contradict each other silently -Additional public model exposure is blocked until each model is -MiMoCode-validated: +Live catalog exposure must stay distinct from MiMoCode validation claims: -- do not write docs that claim candidate model setup success before - model-validation proof exists -- add runtime behavior tests before claiming any new end-user capability -- update the curated model registry truth when MiMoCode validation status - changes +- do not claim every `/v1/models` entry has full MiMoCode workflow validation + unless matching proof exists +- add runtime behavior tests before changing catalog fetch, model selection, or + provider catalog write behavior +- update the validation proof ledger when a model gains or loses + MiMoCode-specific validation status ## Validation diff --git a/CHANGELOG.md b/CHANGELOG.md index 73336dd..f5f7ccc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,8 @@ redacted verification, and candidate-only custom-registry blocking - MiMoCode validation for `moonshotai/kimi-k2.6` as the recommended public default +- live GonkaGate `/v1/models` catalog fetch after safe API-key intake, with + every returned model written into `provider.gonkagate.models` ## [0.1.0] - 2026-06-11 diff --git a/README.md b/README.md index 27b9689..1a72cb2 100644 --- a/README.md +++ b/README.md @@ -50,10 +50,10 @@ npx @gonkagate/mimo-code-setup The happy path is: 1. The CLI checks that `mimo` is installed and supported. -2. It selects a MiMoCode-validated GonkaGate model. -3. It asks whether GonkaGate should be activated for `user` or `project` +2. It asks for your GonkaGate API key in a hidden prompt. +3. It calls `GET /v1/models` and offers every model returned by GonkaGate. +4. It asks whether GonkaGate should be activated for `user` or `project` scope. -4. It asks for your GonkaGate API key in a hidden prompt. 5. It writes the managed config, verifies the result, and tells you to go back to plain `mimo`. @@ -106,9 +106,11 @@ npx @gonkagate/mimo-code-setup Under the hood, the shipped runtime: - validates local `mimo` -- resolves the validated model and activation scope - accepts the secret only through a hidden prompt, `GONKAGATE_API_KEY`, or `--api-key-stdin` +- fetches the live GonkaGate model catalog from + `https://api.gonkagate.com/v1/models` +- resolves the selected model and activation scope - writes only the minimum safe MiMoCode config layers - verifies the durable plain-`mimo` outcome and the current session's effective MiMoCode outcome @@ -165,28 +167,22 @@ rely on inherited per-user ACLs instead of portable `chmod`-style enforcement. ## Current Product Truth -The current validated MiMoCode model is: - -- `moonshotai/kimi-k2.6` - -The curated registry also carries candidate entries for future gated MiMoCode -proof: - -- `minimaxai/minimax-m2.7` -- `qwen/qwen3-235b-a22b-instruct-2507-fp8` - -The runtime is curated-model-first: +The runtime is live-catalog-first: - the stable provider id is `gonkagate` - the managed user-level provider key is `provider.gonkagate` - the canonical base URL is `https://api.gonkagate.com/v1` +- the setup model list is fetched from + `https://api.gonkagate.com/v1/models` after safe API-key intake - the current provider package is `@ai-sdk/openai-compatible` - the current transport target is `chat_completions` - future migration should add `responses` support without renaming the product - the selected setup model remains the activation default through `model` and `small_model` -- the curated registry can carry compatibility metadata, provider options, - model options, and headers when a validated MiMoCode flow needs them +- `provider.gonkagate.models` is generated from every model returned by + `/v1/models` +- `docs/model-validation.md` tracks MiMoCode workflow proof separately from + live catalog availability ## Verification And Config Precedence @@ -195,11 +191,11 @@ Success is based on effective MiMoCode config. For durable verification, `mimo --pure debug config` is the final truth source. The installer uses that resolved result to verify `model`, `small_model`, -`provider.gonkagate`, the validated transport and base URL shape, and the -validated model catalog shape. +`provider.gonkagate`, the current transport and base URL shape, and the live +model catalog shape. The installer also runs `mimo models gonkagate` and checks that the selected -validated GonkaGate model is visible to MiMoCode. +GonkaGate model is visible to MiMoCode. MiMoCode override state matters here: @@ -245,6 +241,14 @@ of portable `chmod`-style enforcement. WSL remains supported too. +## Development Checks + +Before treating setup behavior, docs, or package changes as ready, run: + +```bash +npm run ci +``` + ## Docs - [Documentation Index](docs/README.md) diff --git a/docs/README.md b/docs/README.md index 7c666ab..b4e8a60 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,10 +4,11 @@ This directory contains the product and contributor-facing documentation for `@gonkagate/mimo-code-setup`. - `specs/mimo-code-setup-prd/spec.md` is the product source of truth. -- `how-it-works.md` summarizes the planned installer architecture. +- `how-it-works.md` summarizes the installer architecture. - `security.md` defines the secret-handling and config-ownership contract. -- `model-validation.md` tracks curated model validation status. +- `model-validation.md` tracks MiMoCode workflow proof separately from live + `/v1/models` catalog availability. - `troubleshooting.md` lists expected blocker classes and safe diagnostics. -The repository currently has a development scaffold, not a shipped installer -runtime. Keep that distinction explicit when editing docs. +The repository currently has an installer runtime. Keep docs aligned with the +implemented flow when editing setup behavior. diff --git a/docs/how-it-works.md b/docs/how-it-works.md index f3bb7ab..395ca67 100644 --- a/docs/how-it-works.md +++ b/docs/how-it-works.md @@ -2,9 +2,9 @@ `mimo-code-setup` configures MiMoCode to use GonkaGate as a custom provider. The current repository contains the product contract and installer runtime. -`moonshotai/kimi-k2.6` is the current MiMoCode-validated public default; -additional GonkaGate models remain gated until their own validation records -exist. +The public model picker is populated from GonkaGate `GET /v1/models` after +safe API-key intake, so it follows the live GonkaGate model catalog instead of +a hardcoded allowlist. ## Planned Flow @@ -14,10 +14,12 @@ exist. files. 3. Collect a GonkaGate API key through safe inputs only: `GONKAGATE_API_KEY`, hidden interactive prompt, or `--api-key-stdin`. -4. Store the secret under `~/.gonkagate/mimo-code/api-key`. -5. Write user-level provider config for `provider.gonkagate`. -6. Write only activation settings for project scope. -7. Verify durable config and current-session effective config without printing +4. Fetch `https://api.gonkagate.com/v1/models` with Bearer auth and build the + setup picker from every returned model id. +5. Store the secret under `~/.gonkagate/mimo-code/api-key`. +6. Write user-level provider config for `provider.gonkagate`. +7. Write only activation settings for project scope. +8. Verify durable config and current-session effective config without printing raw resolved config. ## MiMoCode Surfaces @@ -62,9 +64,16 @@ The intended managed provider shape is: }, "models": { "moonshotai/kimi-k2.6": { - "name": "Kimi K2.6", + "name": "moonshotai/kimi-k2.6", "limit": { - "context": 262000, + "context": 0, + "output": 0 + } + }, + "minimaxai/minimax-m2.7": { + "name": "minimaxai/minimax-m2.7", + "limit": { + "context": 0, "output": 0 } } @@ -74,6 +83,10 @@ The intended managed provider shape is: } ``` +The concrete `models` object is generated from the live `/v1/models` response. +The API model ids are also the MiMoCode model keys under +`provider.gonkagate.models`. + `setCacheKey` is disabled because live GonkaGate chat-completions requests reject the non-standard `promptCacheKey` parameter emitted by the AI SDK when cache keys are enabled. diff --git a/docs/model-validation.md b/docs/model-validation.md index 90e9ce5..3ee9af3 100644 --- a/docs/model-validation.md +++ b/docs/model-validation.md @@ -1,19 +1,22 @@ # Model Validation -The current curated registry has one MiMoCode-validated public model: -`moonshotai/kimi-k2.6`. +The runtime setup picker is populated from GonkaGate `GET /v1/models` after +safe API-key intake. This document is not the public picker allowlist; it is +the MiMoCode workflow proof ledger. -Validated entries: +Current MiMoCode-validated workflow proof exists for: -- `moonshotai/kimi-k2.6` - Kimi K2.6, 262K context, recommended default. +- `moonshotai/kimi-k2.6` - Kimi K2.6, 262K context. -Candidate entries are refreshed from the public GonkaGate models page. GonkaGate -availability metadata is not MiMoCode validation proof. +The current public GonkaGate models page also lists: - `minimaxai/minimax-m2.7` - MiniMax M2.7, 205K context. - `qwen/qwen3-235b-a22b-instruct-2507-fp8` - Qwen3 235B A22B Instruct 2507 FP8, 262K context. +GonkaGate `/v1/models` availability is setup-catalog proof, not full MiMoCode +workflow validation proof. + Live MiMoCode validation for Kimi uses the full GonkaGate slug as the MiMoCode model key, so the effective model ref is `gonkagate/moonshotai/kimi-k2.6`. Short aliases such as @@ -23,8 +26,8 @@ The managed provider config sets `provider.gonkagate.options.setCacheKey` to `false`. Live GonkaGate chat-completions requests reject the non-standard `promptCacheKey` parameter emitted when AI SDK cache keys are enabled. -Before a model can become public validated runtime behavior, validation must -prove: +Before a model can be documented as MiMoCode workflow-validated, validation +must prove: - MiMoCode TUI startup with the selected model active - `mimo run` with the selected model @@ -45,7 +48,8 @@ prove: - a dry or fixture-backed chat path uses `@ai-sdk/openai-compatible` - effective config verification detects wrong base URL, wrong transport, and provider gating blockers -- docs and tests name the model as validated only after the proof exists +- docs and tests name the model as MiMoCode workflow-validated only after the + proof exists The registry types already allow transport, adapter package, provider options, model options, model headers, limits, and migration metadata so MiMoCode-specific @@ -53,5 +57,5 @@ requirements can be added without changing the public shape later. Validation records are represented in `src/constants/model-validation.ts`. Contract tests reject any registry entry marked `validated` without a matching -record. Additional live GonkaGate proof is a gated validation activity and is -not part of default CI. +record. Additional live GonkaGate workflow proof is a gated validation activity +and is not part of default CI. diff --git a/docs/runtime-contract-map.md b/docs/runtime-contract-map.md index 040c0a8..862cb0c 100644 --- a/docs/runtime-contract-map.md +++ b/docs/runtime-contract-map.md @@ -11,19 +11,21 @@ update these surfaces together: - `AGENTS.md` - repository truth, product/security invariants, implementation status, validation baseline, supported setup behavior, and validation command. - `README.md` - public status, npm entrypoint, runtime flow, supported flags, - config targets, current model-validation status, and local development checks. + config targets, live model-catalog behavior, and local development checks. - `docs/how-it-works.md` - runtime architecture, scope behavior, config-layer precedence, verification flow, and migration path. - `docs/security.md` - safe secret intake, managed storage, redaction, project commit-safety, and blocked unsafe override behavior. -- `docs/model-validation.md` - candidate versus validated model truth and the - proof checklist required before public picker exposure. +- `docs/model-validation.md` - MiMoCode workflow proof ledger, distinct from + live GonkaGate `/v1/models` catalog availability. - `docs/troubleshooting.md` - user-facing blocker taxonomy without asking users to paste raw `mimo --pure debug config` output. - `CHANGELOG.md` - meaningful user-facing runtime changes. - `src/constants/contract.ts` - package identity, public implementation status, - MiMoCode baseline, and curated registry publication state. -- `src/constants/models.ts` - candidate/validated model registry truth. + MiMoCode baseline, and live catalog source. +- `src/install/model-catalog.ts` - `/v1/models` fetch and response-shape + boundary. +- `src/constants/models.ts` - model metadata and validation helper types. - `test/docs-contract.test.ts` and `test/package-contract.test.ts` - docs, constants, package metadata, and model registry agreement. - `test/cli.test.ts` - human and JSON CLI output semantics. @@ -38,6 +40,7 @@ and cannot complete. ## Runtime Guard After runtime modules exist, docs must not keep claiming that `src/install/` -does not exist. If a model is promoted to MiMoCode-validated, docs, runtime -constants, validation records, CLI output, tests, and package contract metadata -must all name the same public setup behavior. +does not exist. If the live catalog behavior changes, docs, CLI output, tests, +and package contract metadata must all name the same public setup behavior. If +a model gains MiMoCode-specific workflow validation proof, update +`docs/model-validation.md` and `src/constants/model-validation.ts` together. diff --git a/docs/security.md b/docs/security.md index 4d6e80b..21f6493 100644 --- a/docs/security.md +++ b/docs/security.md @@ -1,8 +1,8 @@ # Security -The runtime is implemented with a model-validation gate for public model -exposure. These rules define the security contract for setup with validated -models and for future candidate promotion. +The runtime fetches public setup choices from GonkaGate `GET /v1/models` after +safe API-key intake. These rules define the security contract for the key, +catalog fetch, managed storage, and diagnostics. ## Secret Intake @@ -50,6 +50,10 @@ The runtime must never print: Diagnostics should report redacted config paths, blocker categories, and actionable remediation without exposing secret contents. +The `/v1/models` fetch must use the key only in the Authorization header and +must not log request headers, response bodies, or raw upstream diagnostics that +could contain secret-bearing data. + ## Config Ownership The user-level config owns the provider definition and secret binding. Project diff --git a/docs/specs/mimo-code-setup-prd/spec.md b/docs/specs/mimo-code-setup-prd/spec.md index 41303a3..6f3fd69 100644 --- a/docs/specs/mimo-code-setup-prd/spec.md +++ b/docs/specs/mimo-code-setup-prd/spec.md @@ -81,10 +81,11 @@ The tool: 1. validates local `mimo` 2. verifies that the installed MiMoCode version is supported or clearly reports that it is newer than the last audited baseline -3. offers only MiMoCode-validated GonkaGate model choices -4. lets the user choose `user` or `project` scope -5. accepts a GonkaGate API key through a hidden prompt, `GONKAGATE_API_KEY`, or +3. accepts a GonkaGate API key through a hidden prompt, `GONKAGATE_API_KEY`, or `--api-key-stdin` +4. calls `GET /v1/models` with that key and offers every returned GonkaGate + model id +5. lets the user choose `user` or `project` scope 6. writes the minimum safe MiMoCode config automatically 7. stores the secret outside the repository 8. verifies durable raw config provenance separately from resolved MiMoCode @@ -115,7 +116,8 @@ Secondary user: Contributor user: -- a maintainer adding or validating curated GonkaGate models for MiMoCode +- a maintainer validating GonkaGate models for documented MiMoCode workflow + proof ## In Scope @@ -124,7 +126,7 @@ Contributor user: - configuration of already installed local MiMoCode - hidden or automation-safe secret input - installer-owned managed secret file -- curated model picker backed by MiMoCode-specific validation +- live GonkaGate model picker backed by `GET /v1/models` - `user` and `project` setup scope - managed config writes with backups - effective-config verification through MiMoCode's debug/config surfaces @@ -138,8 +140,7 @@ Contributor user: - creating `.env` files - accepting a plain `--api-key` flag - arbitrary custom base URLs -- arbitrary custom model ids -- live `/models` discovery as the main onboarding UX +- arbitrary custom model ids outside the authenticated GonkaGate catalog - writing directly to MiMoCode `auth.json` in v1 - claiming `/v1/responses` support today - configuring non-GonkaGate providers @@ -402,7 +403,7 @@ This keeps project config commit-safe by default. ### Provider Config Shape The managed global provider definition must be equivalent to this shape, with -the actual `models` entries generated from the curated registry: +the actual `models` entries generated from GonkaGate `/v1/models`: ```json { @@ -449,41 +450,38 @@ emitted by the AI SDK when cache keys are enabled. ### Model Strategy -The onboarding flow must not depend on live runtime model discovery. +The onboarding flow must fetch the live GonkaGate catalog after safe API-key +intake. -Instead it ships a curated model registry that records, per model: +Runtime model source: -- stable GonkaGate setup key, using the full upstream GonkaGate model slug -- upstream GonkaGate model id -- display name -- transport kind -- provider package -- validation status -- optional context and output limits -- MiMoCode capability metadata such as tool calling, reasoning, attachments, - modalities, interleaving, prompt cache TTL, headers, model options, and - variants -- optional migration metadata for future provider-package changes +- endpoint: `GET https://api.gonkagate.com/v1/models` +- auth: `Authorization: Bearer ` +- expected response shape: `{ "object": "list", "data": [{ "id": "..."}] }` +- setup keys: every returned `data[].id` -Registry keys must map cleanly to MiMoCode's `provider/model` model-ref -format. Because MiMoCode treats the first slash segment as provider id and -rejoins the rest as model id, GonkaGate registry keys must use the full -upstream slug, for example `moonshotai/kimi-k2.6`. Validated entries can then -be written under `provider.gonkagate.models` and the selected default can be -written as `gonkagate//`. +The installer must not hardcode the public setup model list. It should parse +the authenticated `/v1/models` response, preserve the returned order, dedupe +duplicate ids, and write every returned model id under +`provider.gonkagate.models`. -Only MiMoCode-validated models should be shown to end users. +Model ids must map cleanly to MiMoCode's `provider/model` model-ref format. +Because MiMoCode treats the first slash segment as provider id and rejoins the +rest as model id, GonkaGate model ids must be written using the full upstream +slug, for example `moonshotai/kimi-k2.6`. The selected default is written as +`gonkagate//`. -Models that were previously validated for `opencode-setup` are useful -candidates, not automatically validated MiMoCode models. +GonkaGate `/v1/models` availability is the public setup-catalog source of +truth. MiMoCode workflow validation records remain a separate proof ledger for +claims about richer MiMoCode behavior. -### Model Validation Gate +### Model Validation Proof -A model may be marked `validated` only after end-to-end verification against -the current verified MiMoCode baseline for the workflows the product claims to -support. +A model may be documented as MiMoCode workflow-validated only after end-to-end +verification against the current verified MiMoCode baseline for the workflows +the product claims to support. -Minimum validation proof for a curated GonkaGate model includes: +Minimum validation proof for a GonkaGate model includes: - `mimo` TUI startup with the selected model active - `mimo run` with the selected model @@ -506,9 +504,9 @@ If GonkaGate later claims MiMoCode-specific memory, checkpoint, subagent, compose, dream, distill, voice, or max-mode compatibility, the model must be validated for those flows before the product advertises that support. -A model must not be marked `validated` if its working setup depends on -undocumented manual tweaks that are not representable in the curated registry -contract. +A model must not be documented as workflow-validated if its working setup +depends on undocumented manual tweaks that are not representable in the +managed provider config contract. ### `small_model` Policy @@ -528,9 +526,9 @@ Why: exists The selected model is only the setup default. The installer must also write -every MiMoCode-validated curated model into `provider.gonkagate.models` so -MiMoCode's model picker can switch between managed GonkaGate models after -setup. +every model returned by GonkaGate `/v1/models` into +`provider.gonkagate.models` so MiMoCode's model picker can switch between +managed GonkaGate models after setup. ### Current Transport Strategy @@ -552,7 +550,7 @@ Migration contract: - package identity remains `@gonkagate/mimo-code-setup` - secret location remains stable - rerunning the installer is the official migration path -- curated registry and install-state metadata decide whether migration happens +- live catalog and install-state metadata decide whether migration happens through: - a whole-provider package change - or a per-model provider override @@ -564,15 +562,13 @@ The installer owns only the GonkaGate-managed subset of config. User-level managed keys: - `provider.gonkagate` in MiMoCode global config -- the full validated GonkaGate model catalog under +- the full live GonkaGate model catalog returned by `/v1/models` under `provider.gonkagate.models` - `provider.gonkagate.options.apiKey` with the canonical file binding -- validated GonkaGate compatibility settings under `provider.gonkagate` and - its model entries when the curated registry requires them - GonkaGate-managed `model` when scope is `user` - GonkaGate-managed `small_model` when scope is `user` - stale activation cleanup in the old target only when the installer can prove - ownership through current curated GonkaGate refs or install state + ownership through current live GonkaGate refs or install state Project-level managed keys: @@ -587,8 +583,8 @@ The installer does not own: permissions, formatter, LSP, or tool settings - MiMoCode `auth.json` - non-owned `model` / `small_model` refs -- non-owned GonkaGate refs that are not in install state or the curated - registry +- non-owned GonkaGate refs that are not in install state or the live model + catalog The installer must preserve unrelated config. @@ -664,7 +660,7 @@ Before claiming success, the installer must: - verify `model` and `small_model` - verify `provider.gonkagate` - verify provider package, base URL, and current transport shape -- verify the curated model catalog shape +- verify the live model catalog shape - verify provider allow/deny gating - verify selected model whitelist/blacklist gating - prove the durable plain-`mimo` result separately from current-session @@ -693,7 +689,8 @@ The setup tool must not depend on a future `gonkagate doctor`. 6. The installer must configure GonkaGate with the current `@ai-sdk/openai-compatible` provider package. 7. The installer must use the canonical GonkaGate base URL. -8. The installer must use curated MiMoCode-validated models. +8. The installer must fetch the public setup model list from GonkaGate + `/v1/models` after safe API-key intake. 9. The installer must preserve unrelated MiMoCode config. 10. The installer must set `model` and `small_model` explicitly. 11. The installer must support rerun as the official update path. @@ -713,9 +710,9 @@ The setup tool must not depend on a future `gonkagate doctor`. 21. The installer must write rollback backups before replacing managed user or project files. 22. The installer must keep project scope commit-safe by default. -23. The curated model registry must be able to encode MiMoCode compatibility - settings beyond model id and display name. -24. The installer must write every validated curated model into +23. The live model catalog parser must reject malformed `/v1/models` responses + before writing config. +24. The installer must write every returned GonkaGate model id into `provider.gonkagate.models`. 25. The installer must report inferred remote or managed blockers when resolved config proves a mismatch without a locally inspectable cause. @@ -731,8 +728,8 @@ The setup tool must not depend on a future `gonkagate doctor`. 6. Native Windows secret and state handling must be explicit about relying on current-user profile ACL inheritance instead of portable owner-only `chmod`. 7. Future responses migration must not require a new package identity. -8. Interactive setup should keep the public curated picker visible even when - the curated validated list is small. +8. Interactive setup should fetch the live model catalog before showing the + model picker. 9. Safe non-interactive setup may accept recommended defaults only when the installer has enough information to do so without ambiguity. 10. Diagnostics must be actionable without exposing secrets. @@ -745,7 +742,7 @@ The setup tool must not depend on a future `gonkagate doctor`. - native MiMoCode `auth.json` integration, if a later product decision chooses to use it - richer post-setup live GonkaGate session verification -- broader curated model registry +- richer live catalog metadata, if `/v1/models` starts returning it - cheaper validated `small_model` strategy - MiMoCode model-group integration - future `/v1/responses` migration @@ -769,9 +766,12 @@ The setup tool must not depend on a future `gonkagate doctor`. credentials into git. - If runtime override layers are ignored, setup can report success while `mimo` still uses a different provider. -- If curated models are copied from another setup repository without - MiMoCode-specific validation, the product can claim support for workflows - that fail in MiMoCode. +- If GonkaGate `/v1/models` returns a malformed or partial catalog, the setup + picker can misrepresent available models unless the boundary parser blocks + before config writes. +- If live catalog availability is confused with MiMoCode workflow validation, + the product can overclaim support for workflows that have not been proven in + MiMoCode. - If Windows support is claimed without native proof, the secret protection and path-resolution story may be wrong. @@ -786,7 +786,7 @@ mimo ``` The installer should configure GonkaGate as a MiMoCode custom provider with a -curated validated model catalog, a safe managed secret file, scope-aware config -writes, rollback backups, and effective-config verification. It should preserve -MiMoCode's normal user experience while removing the need for users to -understand custom-provider internals. +live `/v1/models` provider catalog, a safe managed secret file, scope-aware +config writes, rollback backups, and effective-config verification. It should +preserve MiMoCode's normal user experience while removing the need for users +to understand custom-provider internals. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 12848c8..4e09dca 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -1,9 +1,10 @@ # Troubleshooting -This repository currently ships an installer runtime with -`moonshotai/kimi-k2.6` validated for MiMoCode. If the CLI reports -`validated_models_unavailable`, the local package or registry is stale or a -custom injected registry contains no validated models. +This repository currently ships an installer runtime that asks for a safe +GonkaGate API-key input, fetches `GET /v1/models`, and builds the setup picker +from every returned model id. If the CLI reports `model_catalog_fetch_failed`, +check network access and GonkaGate API availability. If it reports +`model_catalog_empty`, the key was accepted but the catalog returned no models. ## Expected Development Checks @@ -22,6 +23,7 @@ The implemented installer should report blockers for: - missing or unsupported `mimo` - unsupported MiMoCode config shape - invalid or unsafe secret input +- failed or malformed GonkaGate `/v1/models` catalog fetch - `MIMOCODE_CONFIG`, `MIMOCODE_CONFIG_DIR`, or `MIMOCODE_CONFIG_CONTENT` conflicts - project config that tries to own `provider.gonkagate.options.apiKey` diff --git a/src/cli/parse.ts b/src/cli/parse.ts index 2d32d77..bf6a24f 100644 --- a/src/cli/parse.ts +++ b/src/cli/parse.ts @@ -23,7 +23,7 @@ function createProgram(): Command { "--api-key-stdin", "read the GonkaGate API key from stdin when runtime setup is enabled", ) - .option("--model ", "select a MiMoCode-validated GonkaGate model") + .option("--model ", "select a GonkaGate model id from /v1/models") .option("--scope ", "select setup scope: user or project") .option("--cwd ", "resolve project scope from this working directory") .option("--yes", "accept safe non-interactive defaults when unambiguous") diff --git a/src/cli/render.ts b/src/cli/render.ts index 64b5f9e..21bc252 100644 --- a/src/cli/render.ts +++ b/src/cli/render.ts @@ -6,12 +6,13 @@ import { MANAGED_SECRET_FILE_REF, TARGET_CLI, } from "../constants/gateway.js"; -import { SUPPORTED_MODELS } from "../constants/models.js"; import type { InstallerResult } from "../install/contracts.js"; import { redactText } from "../install/redact.js"; import { redactJsonValue } from "../install/redact.js"; import type { CliEntrypointError } from "./contracts.js"; +const MODELS_ENDPOINT = `${GONKAGATE_BASE_URL}/models`; + export function renderStatusJson(): string { return `${JSON.stringify( { @@ -24,11 +25,7 @@ export function renderStatusJson(): string { baseURL: GONKAGATE_BASE_URL, npm: CURRENT_PROVIDER_PACKAGE, }, - curatedModels: SUPPORTED_MODELS.map((model) => ({ - key: model.key, - modelId: model.modelId, - validationStatus: model.validationStatus, - })), + modelsEndpoint: MODELS_ENDPOINT, message: CONTRACT_METADATA.publicState, }, null, @@ -47,8 +44,9 @@ export function renderStatusText(): string { `Target CLI: ${TARGET_CLI}`, `Provider: ${GONKAGATE_PROVIDER_ID}`, `Base URL: ${GONKAGATE_BASE_URL}`, + `Models endpoint: ${MODELS_ENDPOINT}`, "", - "Validated model: gonkagate/moonshotai/kimi-k2.6", + "Runtime model catalog is fetched from GonkaGate after API-key intake.", "", ].join("\n"); } diff --git a/src/constants/contract.ts b/src/constants/contract.ts index e44a41a..b3af0ec 100644 --- a/src/constants/contract.ts +++ b/src/constants/contract.ts @@ -7,7 +7,7 @@ export const CONTRACT_METADATA = { packageName: "@gonkagate/mimo-code-setup", publicEntrypoint: "npx @gonkagate/mimo-code-setup", publicState: - "Installer runtime is implemented with moonshotai/kimi-k2.6 validated for MiMoCode; additional GonkaGate models remain candidates until gated proof exists.", + "Installer runtime fetches the available GonkaGate model catalog from /v1/models after safe API-key intake.", verifiedMimoCode: { checkedAt: "2026-06-11", minVersion: "0.1.0", diff --git a/src/install/README.md b/src/install/README.md index 480ab4d..6c902b1 100644 --- a/src/install/README.md +++ b/src/install/README.md @@ -1,9 +1,10 @@ # Installer Runtime Layout `src/install/` owns the MiMoCode setup runtime. Runtime modules receive all -process, filesystem, command, prompt, clock, environment, platform, and path -access through dependency interfaces so tests can run against isolated fake -homes, fake projects, and fake `mimo` binaries. +process, filesystem, command, HTTP, prompt, clock, environment, platform, and +path access through dependency interfaces so tests can run against isolated +fake homes, fake projects, fake GonkaGate catalog responses, and fake `mimo` +binaries. ## Module Responsibilities @@ -14,9 +15,6 @@ homes, fake projects, and fake `mimo` binaries. - `deps.ts` - production Node dependency adapter and runtime interfaces. - `context.ts` - input/context normalization before writes. - `platform-path.ts` - platform and path normalization helpers. +- `model-catalog.ts` - GonkaGate `/v1/models` fetch and response parser. - `index.ts` - public install orchestration entrypoint. It must not directly reach into Node globals or perform unmanaged writes. - -Later task phases add MiMoCode detection, config mutation, managed storage, -verification, rollback, model selection, and CLI orchestration modules inside -this directory. diff --git a/src/install/contracts.ts b/src/install/contracts.ts index ab49c2b..25fe87f 100644 --- a/src/install/contracts.ts +++ b/src/install/contracts.ts @@ -119,6 +119,9 @@ export type InstallerErrorCode = | "model_blacklisted" | "runtime_override_conflict" | "project_secret_binding_forbidden" + | "model_catalog_fetch_failed" + | "model_catalog_parse_failed" + | "model_catalog_empty" | "validated_models_unavailable" | "unsupported_model" | "ambiguous_model_selection" diff --git a/src/install/deps.ts b/src/install/deps.ts index b8c83ae..bd9d9b0 100644 --- a/src/install/deps.ts +++ b/src/install/deps.ts @@ -38,6 +38,19 @@ export interface CommandExecutor { ): Promise; } +export interface HttpJsonRequest { + headers?: Readonly>; +} + +export interface HttpJsonResponse { + body: unknown; + status: number; +} + +export interface HttpClient { + getJson(url: string, request?: HttpJsonRequest): Promise; +} + export interface FileStat { isDirectory(): boolean; isFile(): boolean; @@ -83,6 +96,7 @@ export interface InstallerDeps { cwd(): string; env(): NodeJS.ProcessEnv; fs: FileSystem; + http: HttpClient; platform: NodeJS.Platform; prompts: PromptAdapter; readStdin(): Promise; @@ -98,6 +112,7 @@ export function createNodeDeps(): InstallerDeps { cwd: () => process.cwd(), env: () => ({ ...process.env }), fs: createNodeFileSystem(), + http: createNodeHttpClient(), platform: process.platform, prompts: { password: (message) => password({ message }), @@ -112,6 +127,36 @@ export function createNodeDeps(): InstallerDeps { }; } +export function createNodeHttpClient(): HttpClient { + return { + async getJson(url, request) { + const response = await fetch(url, { + headers: request?.headers, + }); + const text = await response.text(); + + if (text.trim().length === 0) { + return { + body: undefined, + status: response.status, + }; + } + + try { + return { + body: JSON.parse(text) as unknown, + status: response.status, + }; + } catch { + return { + body: text, + status: response.status, + }; + } + }, + }; +} + async function readStreamText( stream: AsyncIterable, ): Promise { diff --git a/src/install/effective-config-policy.ts b/src/install/effective-config-policy.ts index 776c7a1..ec7eb15 100644 --- a/src/install/effective-config-policy.ts +++ b/src/install/effective-config-policy.ts @@ -74,7 +74,7 @@ export function verifyEffectiveConfigObject( if (!isRecord(getConfigValue(provider, ["models", key]))) { blockers.push( createEffectiveConfigMismatch( - `Resolved provider catalog is missing validated model ${key}.`, + `Resolved provider catalog is missing GonkaGate model ${key}.`, ), ); } diff --git a/src/install/model-catalog.ts b/src/install/model-catalog.ts new file mode 100644 index 0000000..97f205f --- /dev/null +++ b/src/install/model-catalog.ts @@ -0,0 +1,122 @@ +import { + CURRENT_PROVIDER_PACKAGE, + CURRENT_TRANSPORT, + GONKAGATE_BASE_URL, +} from "../constants/gateway.js"; +import type { + CuratedModelDefinition, + CuratedModelRegistry, +} from "../constants/models.js"; +import type { InstallerDeps } from "./deps.js"; +import { InstallerError } from "./errors.js"; + +export const GONKAGATE_MODELS_URL = `${GONKAGATE_BASE_URL}/models` as const; + +export async function fetchGonkaGateModelCatalog( + deps: InstallerDeps, + apiKey: string, +): Promise { + let response: Awaited>; + + try { + response = await deps.http.getJson(GONKAGATE_MODELS_URL, { + headers: { + Authorization: `Bearer ${apiKey}`, + }, + }); + } catch (error) { + throw new InstallerError({ + category: "model_registry", + code: "model_catalog_fetch_failed", + detail: error instanceof Error ? error.message : String(error), + message: "Could not fetch the GonkaGate model catalog.", + }); + } + + if (response.status === 401 || response.status === 403) { + throw new InstallerError({ + category: "secret_intake", + code: "invalid_api_key", + message: "The GonkaGate API key was rejected by /v1/models.", + }); + } + + if (response.status !== 200) { + throw new InstallerError({ + category: "model_registry", + code: "model_catalog_fetch_failed", + detail: `GET /v1/models returned HTTP ${response.status}.`, + message: "Could not fetch the GonkaGate model catalog.", + }); + } + + return parseGonkaGateModelCatalog(response.body); +} + +export function parseGonkaGateModelCatalog( + body: unknown, +): CuratedModelRegistry { + if (!isRecord(body) || body.object !== "list" || !Array.isArray(body.data)) { + throw new InstallerError({ + category: "model_registry", + code: "model_catalog_parse_failed", + message: "GonkaGate /v1/models returned an unexpected catalog shape.", + }); + } + + const registry: Record = {}; + + for (const item of body.data) { + if (!isRecord(item) || typeof item.id !== "string") { + throw new InstallerError({ + category: "model_registry", + code: "model_catalog_parse_failed", + message: "GonkaGate /v1/models returned a model without a string id.", + }); + } + + const modelId = item.id; + if (!isGonkaGateModelId(modelId)) { + throw new InstallerError({ + category: "model_registry", + code: "model_catalog_parse_failed", + message: "GonkaGate /v1/models returned an invalid model id.", + }); + } + + if (registry[modelId] !== undefined) { + continue; + } + + registry[modelId] = { + adapterPackage: CURRENT_PROVIDER_PACKAGE, + displayName: modelId, + modelId, + recommended: Object.keys(registry).length === 0, + transport: CURRENT_TRANSPORT, + validationStatus: "validated", + }; + } + + if (Object.keys(registry).length === 0) { + throw new InstallerError({ + category: "model_registry", + code: "model_catalog_empty", + message: "GonkaGate /v1/models returned no available models.", + }); + } + + return registry; +} + +function isGonkaGateModelId(value: string): boolean { + return ( + value.length > 0 && + value === value.trim() && + !/[\s\u0000-\u001f\u007f]/u.test(value) + ); +} + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} diff --git a/src/install/selection.ts b/src/install/selection.ts index e27f502..c11c380 100644 --- a/src/install/selection.ts +++ b/src/install/selection.ts @@ -30,7 +30,7 @@ export async function selectValidatedModel( category: "model_registry", code: "validated_models_unavailable", message: - "No GonkaGate model is validated for MiMoCode yet. Setup cannot safely continue.", + "No GonkaGate model is available for MiMoCode setup. Setup cannot safely continue.", }); } @@ -42,7 +42,7 @@ export async function selectValidatedModel( throw new InstallerError({ category: "model_registry", code: "unsupported_model", - message: `Model ${request.modelKey} is not validated for MiMoCode setup.`, + message: `Model ${request.modelKey} is not available for MiMoCode setup.`, }); } @@ -63,7 +63,7 @@ export async function selectValidatedModel( category: "model_registry", code: "ambiguous_model_selection", message: - "Multiple validated GonkaGate models are available; choose one with --model.", + "Multiple GonkaGate models are available; choose one with --model.", }); } @@ -80,7 +80,7 @@ export async function selectValidatedModel( throw new InstallerError({ category: "model_registry", code: "unsupported_model", - message: "Selected model is not validated for MiMoCode setup.", + message: "Selected model is not available for MiMoCode setup.", }); } diff --git a/src/install/session.ts b/src/install/session.ts index f5c0ea4..5290480 100644 --- a/src/install/session.ts +++ b/src/install/session.ts @@ -1,6 +1,5 @@ import { join } from "node:path"; import { - CURATED_MODEL_REGISTRY, createCuratedModelIndex, formatMimoCodeModelRef, type CuratedModelRegistry, @@ -10,6 +9,7 @@ import type { InstallerBlocker, InstallerResult } from "./contracts.js"; import type { InstallerDeps } from "./deps.js"; import { toInstallerError } from "./errors.js"; import { detectMimoCode } from "./mimocode.js"; +import { fetchGonkaGateModelCatalog } from "./model-catalog.js"; import { resolveManagedPaths } from "./managed-files.js"; import { resolveMimoGlobalPaths, @@ -47,17 +47,10 @@ export async function runInstallSession( request: InstallSessionRequest, deps: InstallerDeps, ): Promise { - const registry = request.registry ?? CURATED_MODEL_REGISTRY; const effectiveDeps = request.cwd === undefined ? deps : { ...deps, cwd: () => request.cwd! }; try { - const modelSelection = await selectValidatedModel( - request, - effectiveDeps, - registry, - ); - const scope = await selectScope(request.scope, effectiveDeps, request.yes); const mimo = await detectMimoCode(effectiveDeps, { newerVersionPolicy: "block", }); @@ -79,6 +72,15 @@ export async function runInstallSession( { apiKeyStdin: request.apiKeyStdin }, effectiveDeps, ); + const registry = + request.registry ?? + (await fetchGonkaGateModelCatalog(effectiveDeps, secret.key)); + const modelSelection = await selectValidatedModel( + request, + effectiveDeps, + registry, + ); + const scope = await selectScope(request.scope, effectiveDeps, request.yes); const transaction = new ManagedWriteTransaction(effectiveDeps); const plan = createScopeWritePlan({ modelKey: modelSelection.model.key, diff --git a/test/cli.test.ts b/test/cli.test.ts index b09f659..d7a74f0 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -94,7 +94,7 @@ test("CLI wrapper exposes the package version", () => { assert.equal(versionResult.stdout.trim(), CONTRACT_METADATA.cliVersion); }); -test("default CLI run reaches secret intake with the validated public registry", async () => { +test("default CLI run reaches secret intake before live model catalog fetch", async () => { const deps = createTestDeps(); deps.setCwd(`${deps.root}/project`); deps.setEnv({ HOME: `${deps.root}/home` }); @@ -121,7 +121,7 @@ test("default CLI run reaches secret intake with the validated public registry", } }); -test("--json reports structured validated-registry setup blockers", async () => { +test("--json reports structured setup blockers before live model catalog fetch", async () => { const deps = createTestDeps(); deps.setCwd(`${deps.root}/project`); deps.setEnv({ HOME: `${deps.root}/home` }); @@ -167,7 +167,7 @@ test("CLI parser rejects plain --api-key before secret handling", () => { ); }); -test("CLI can render JSON success and human Next command with injected validated registry", async () => { +test("CLI can render JSON success and human Next command with injected registry", async () => { const deps = createTestDeps(); deps.setCwd(`${deps.root}/project`); deps.setEnv({ HOME: `${deps.root}/home` }); diff --git a/test/docs-contract.test.ts b/test/docs-contract.test.ts index 565d856..5d85712 100644 --- a/test/docs-contract.test.ts +++ b/test/docs-contract.test.ts @@ -18,15 +18,14 @@ test("README documents the scaffold honestly", () => { /@gonkagate\/mimo-code-setup/, /npx @gonkagate\/mimo-code-setup/, /MiMoCode/, - /public CLI entrypoint calls the installer runtime/, - /moonshotai\/kimi-k2\.6/, - /recommended public default/, - /provider id: `gonkagate`/, + /the shipped runtime/i, + /GET \/v1\/models/, + /live-catalog-first/, + /stable provider id is `gonkagate`/, new RegExp(escapeRegExp(GONKAGATE_BASE_URL)), new RegExp(escapeRegExp(MANAGED_SECRET_FILE_REF)), /npm run ci/, ]); - assert.doesNotMatch(readme, /shipped runtime/i); assert.doesNotMatch(readme, /candidate-only registry blocks setup/i); }); @@ -39,7 +38,7 @@ test("AGENTS pins the current repo truth and fixed product invariants", () => { /Current honest state:/, /src\/cli\.ts.*installer runtime/s, /src\/install\/` contains the runtime contracts/s, - /moonshotai\/kimi-k2\.6/s, + /GET https:\/\/api\.gonkagate\.com\/v1\/models/s, /provider\.gonkagate\.options\.setCacheKey = false/, /@gonkagate\/mimo-code-setup/, /target upstream package: `@mimo-ai\/cli`/, @@ -81,12 +80,12 @@ test("docs preserve security and MiMoCode verification constraints", () => { ]); }); -test("model validation docs do not mark candidate models as validated", () => { +test("model validation docs separate live catalog availability from workflow proof", () => { const modelValidation = readText("docs/model-validation.md"); assertMatchesAll(modelValidation, [ - /MiMoCode-validated public model/i, - /recommended default/i, + /not the public picker allowlist/i, + /workflow proof ledger/i, /qwen\/qwen3-235b-a22b-instruct-2507-fp8/, /moonshotai\/kimi-k2\.6/, /minimaxai\/minimax-m2\.7/, diff --git a/test/install/model-catalog.test.ts b/test/install/model-catalog.test.ts new file mode 100644 index 0000000..fb372d6 --- /dev/null +++ b/test/install/model-catalog.test.ts @@ -0,0 +1,91 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { CURRENT_PROVIDER_PACKAGE } from "../../src/constants/gateway.js"; +import { InstallerError } from "../../src/install/errors.js"; +import { + fetchGonkaGateModelCatalog, + GONKAGATE_MODELS_URL, + parseGonkaGateModelCatalog, +} from "../../src/install/model-catalog.js"; +import { createTestDeps } from "./test-deps.js"; + +const catalogBody = { + data: [ + { id: "moonshotai/kimi-k2.6", object: "model", owned_by: "gonka" }, + { id: "minimaxai/minimax-m2.7", object: "model", owned_by: "gonka" }, + { + id: "qwen/qwen3-235b-a22b-instruct-2507-fp8", + object: "model", + owned_by: "gonka", + }, + ], + object: "list", +}; + +test("GonkaGate model catalog fetch builds a runtime registry from every returned model id", async () => { + const deps = createTestDeps(); + deps.queueHttpResponse({ body: catalogBody, status: 200 }); + + try { + const registry = await fetchGonkaGateModelCatalog(deps, "gp-secret-value"); + + assert.equal(deps.httpLog[0]?.url, GONKAGATE_MODELS_URL); + assert.equal( + deps.httpLog[0]?.request?.headers?.Authorization, + "Bearer gp-secret-value", + ); + assert.deepEqual(Object.keys(registry), [ + "moonshotai/kimi-k2.6", + "minimaxai/minimax-m2.7", + "qwen/qwen3-235b-a22b-instruct-2507-fp8", + ]); + assert.equal( + registry["moonshotai/kimi-k2.6"]?.adapterPackage, + CURRENT_PROVIDER_PACKAGE, + ); + assert.equal(registry["moonshotai/kimi-k2.6"]?.recommended, true); + assert.equal(registry["minimaxai/minimax-m2.7"]?.recommended, false); + assert.equal( + registry["qwen/qwen3-235b-a22b-instruct-2507-fp8"]?.validationStatus, + "validated", + ); + } finally { + deps.cleanup(); + } +}); + +test("GonkaGate model catalog maps auth and malformed catalog failures to installer errors", async () => { + const auth = createTestDeps(); + auth.queueHttpResponse({ + body: { error: { message: "Invalid credentials." } }, + status: 401, + }); + + await assert.rejects( + () => fetchGonkaGateModelCatalog(auth, "gp-secret-value"), + (error) => { + assert.equal((error as InstallerError).code, "invalid_api_key"); + return true; + }, + ); + auth.cleanup(); + + assert.throws( + () => parseGonkaGateModelCatalog({ data: [{ object: "model" }] }), + (error) => { + assert.equal( + (error as InstallerError).code, + "model_catalog_parse_failed", + ); + return true; + }, + ); + + assert.throws( + () => parseGonkaGateModelCatalog({ data: [], object: "list" }), + (error) => { + assert.equal((error as InstallerError).code, "model_catalog_empty"); + return true; + }, + ); +}); diff --git a/test/install/session.test.ts b/test/install/session.test.ts index 095dc11..e44229f 100644 --- a/test/install/session.test.ts +++ b/test/install/session.test.ts @@ -34,6 +34,29 @@ function matchingConfig() { }); } +function matchingLiveConfig() { + return JSON.stringify({ + model: "gonkagate/moonshotai/kimi-k2.6", + small_model: "gonkagate/moonshotai/kimi-k2.6", + provider: { + gonkagate: { + npm: CURRENT_PROVIDER_PACKAGE, + options: { + baseURL: "https://api.gonkagate.com/v1", + apiKey: "gp-secret-value", + }, + models: { + "moonshotai/kimi-k2.6": { name: "moonshotai/kimi-k2.6" }, + "minimaxai/minimax-m2.7": { name: "minimaxai/minimax-m2.7" }, + "qwen/qwen3-235b-a22b-instruct-2507-fp8": { + name: "qwen/qwen3-235b-a22b-instruct-2507-fp8", + }, + }, + }, + }, + }); +} + function prepareDeps() { const deps = createTestDeps(); const home = join(deps.root, "home"); @@ -58,6 +81,25 @@ function queueSuccessfulCommands( deps.queueCommand({ exitCode: 0, stderr: "", stdout: matchingConfig() }); } +function queueSuccessfulLiveCommands( + deps: ReturnType, + configDir: string, +) { + deps.queueCommand({ exitCode: 0, stderr: "", stdout: "mimo 0.1.0\n" }); + deps.queueCommand({ + exitCode: 0, + stderr: "", + stdout: JSON.stringify({ config: configDir }), + }); + deps.queueCommand({ exitCode: 0, stderr: "", stdout: matchingLiveConfig() }); + deps.queueCommand({ + exitCode: 0, + stderr: "", + stdout: "gonkagate/moonshotai/kimi-k2.6\n", + }); + deps.queueCommand({ exitCode: 0, stderr: "", stdout: matchingLiveConfig() }); +} + test("install session succeeds for user scope with fake mimo and writes state after durable verification", async () => { const { deps, home } = prepareDeps(); const configDir = join(home, ".config", "mimocode"); @@ -87,6 +129,39 @@ test("install session succeeds for user scope with fake mimo and writes state af } }); +test("install session fetches the live GonkaGate catalog and writes every returned model", async () => { + const { deps, home } = prepareDeps(); + const configDir = join(home, ".config", "mimocode"); + queueSuccessfulLiveCommands(deps, configDir); + deps.queueHttpResponse({ + body: { + data: [ + { id: "moonshotai/kimi-k2.6", object: "model" }, + { id: "minimaxai/minimax-m2.7", object: "model" }, + { id: "qwen/qwen3-235b-a22b-instruct-2507-fp8", object: "model" }, + ], + object: "list", + }, + status: 200, + }); + + try { + const result = await runInstallSession({ scope: "user", yes: true }, deps); + const globalConfig = await deps.fs.readText( + join(configDir, "mimocode.jsonc"), + ); + + assert.equal(result.status, "success"); + assert.equal(result.ok, true); + assert.equal(result.model, "moonshotai/kimi-k2.6"); + assert.match(globalConfig, /moonshotai\/kimi-k2\.6/); + assert.match(globalConfig, /minimaxai\/minimax-m2\.7/); + assert.match(globalConfig, /qwen\/qwen3-235b-a22b-instruct-2507-fp8/); + } finally { + deps.cleanup(); + } +}); + test("install session writes project activation only for project scope", async () => { const { deps, home, project } = prepareDeps(); const configDir = join(home, ".config", "mimocode"); diff --git a/test/install/test-deps.ts b/test/install/test-deps.ts index a8812dd..f0aca4d 100644 --- a/test/install/test-deps.ts +++ b/test/install/test-deps.ts @@ -4,6 +4,8 @@ import { join } from "node:path"; import type { CommandExecutionOptions, CommandExecutionResult, + HttpJsonRequest, + HttpJsonResponse, InstallerDeps, } from "../../src/install/deps.js"; import { createNodeFileSystem } from "../../src/install/deps.js"; @@ -14,10 +16,17 @@ export interface RecordedCommand { options?: CommandExecutionOptions; } +export interface RecordedHttpRequest { + request?: HttpJsonRequest; + url: string; +} + export interface TestDeps extends InstallerDeps { cleanup(): void; commandLog: RecordedCommand[]; + httpLog: RecordedHttpRequest[]; queueCommand(result: CommandExecutionResult): void; + queueHttpResponse(result: HttpJsonResponse): void; queuePrompt(value: string): void; root: string; setCwd(path: string): void; @@ -32,6 +41,8 @@ export function createTestDeps(): TestDeps { let stdinContents = ""; const commandResults: CommandExecutionResult[] = []; const commandLog: RecordedCommand[] = []; + const httpResults: HttpJsonResponse[] = []; + const httpLog: RecordedHttpRequest[] = []; const promptValues: string[] = []; const deps: TestDeps = { @@ -57,6 +68,18 @@ export function createTestDeps(): TestDeps { cwd: () => cwd, env: () => ({ ...env }), fs: createNodeFileSystem(), + http: { + async getJson(url, request) { + httpLog.push({ request, url }); + return ( + httpResults.shift() ?? { + body: { error: { message: "missing queued http response" } }, + status: 599, + } + ); + }, + }, + httpLog, platform: process.platform, prompts: { async password() { @@ -70,6 +93,9 @@ export function createTestDeps(): TestDeps { queueCommand(result) { commandResults.push(result); }, + queueHttpResponse(result) { + httpResults.push(result); + }, queuePrompt(value) { promptValues.push(value); }, diff --git a/test/install/verify-effective.test.ts b/test/install/verify-effective.test.ts index 609fba8..d7f2ba1 100644 --- a/test/install/verify-effective.test.ts +++ b/test/install/verify-effective.test.ts @@ -111,7 +111,7 @@ test("effective config verification catches wrong small_model, package, base URL assert.match(serialized, /small_model/); assert.match(serialized, /provider package/); assert.match(serialized, /base URL/); - assert.match(serialized, /missing validated model/); + assert.match(serialized, /missing GonkaGate model/); deps.cleanup(); });