Commit Graph
18 Commits
Author SHA1 Message Date
ZacharyZhang-NY f825132983 Add Cerebras platform + generalize StrictOpenAi dialect (provider 10)
The 13th registry row: id "cerebras", CEREBRAS_API_KEY > auth.json
"cerebras" scope, https://api.cerebras.ai/v1 with KIGI_CEREBRAS_BASE_URL
override, Bearer, ChatCompletions, enrichment-backed metadata
(models_dev_id cerebras).

Cerebras' catalog is all chat LLMs (no embedding/tts pollution) and its
/models is minimal (ids only), so restrict_to_enriched=FALSE: keep every
live model, enrich the known ones (context + effort menus low/medium/high),
unknown ones keep the default context. The e2e pins this enrich-without-
restrict path (new — prior enrichment providers all used restrict=true).

Review caught a likely-DOA defect: Cerebras uses strict
additionalProperties:false validation (confirmed 400-rejecting store,
maxTokens, thinking, nested reasoning_content), and stream_options is not
in its schema — so Passthrough (which keeps the stream_options.include_usage
kigi injects on every streaming request) would very likely 400 all
streaming. Generalized ChatCompat::Mistral -> ChatCompat::StrictOpenAi
(serde alias "mistral" keeps pre-rename persisted sessions loading), which
strips stream_options + private fields for any strict OpenAI-compat
validator; both Mistral and Cerebras now map to it. Future strict-validator
candidates (NVIDIA/Azure/Xiaomi/OpenCode) noted for the same check.

reasoning_effort (incl. "none") passes through; /v1/models requires auth
so key validation works; console cloud.cerebras.ai.
2026-07-21 14:04:34 -04:00
ZacharyZhang-NY 0a147bd8a5 Add Together AI platform + bare-array listing tolerance (provider 9)
The 12th registry row: id "together", TOGETHER_API_KEY > auth.json
"together" scope, https://api.together.xyz/v1 with KIGI_TOGETHER_BASE_URL
override, Bearer, ChatCompletions + Passthrough, enrichment-backed
metadata (models_dev_id togetherai) with the tool-calling listing
restriction (Together's listing mixes chat/embedding/rerank/image types).

Together's GET /v1/models returns a BARE JSON ARRAY, not the OpenAI
{object:list,data:[]} envelope. New shared parser parse_openai_listing
tolerates both shapes (sniffs the top-level [ vs { for accurate
diagnostics); every OpenAI-listing provider now routes through it, with
byte-equivalent envelope behavior (verified: Groq/Google/OpenRouter
unchanged) and non-silent errors. Together's org/Model ids match the
togetherai snapshot keys exactly (verified), so restrict_to_enriched keeps
the ~27 tool-calling models without the id-shape trap.

Review: ship-ready, shared-parser change proven strictly-additive and safe,
registry/e2e/dialect all correct. Backlog logged: kigi ignores the wire
per-model  field, so a live Together chat model absent from
models.dev is dropped until indexed — a future wire- filter would
unlock the fresh full catalog.
2026-07-21 13:16:20 -04:00
ZacharyZhang-NY e7dbb98207 Add OpenRouter platform: wire-served metadata (provider 8)
The 11th registry row and the first THIRD-PARTY wire_serves_metadata=true
provider: id "openrouter", OPENROUTER_API_KEY > auth.json "openrouter"
scope, https://openrouter.ai/api/v1 (note /api/v1) with
KIGI_OPENROUTER_BASE_URL override, Bearer, OpenAI listing +
ChatCompletions + Passthrough.

OpenRouter's public /models serves context_length for every model
(verified live: 340/340), so it needs NO enrichment: models_dev_id=None,
wire_serves_metadata=true, restrict_to_enriched=false. An OpenRouter-only
user makes zero models.dev calls; context comes straight from the listing.
Slashed ids (anthropic/claude-opus-4.8) round-trip through the managed key
via the first-slash split; the native id rides the wire. The e2e pins all
of this with the models.dev refresh disabled.

Review-confirmed defect fixed (and independently re-verified with live
curls): OpenRouter's /models is PUBLIC — GET /models returns 200 for ANY
key — so login key-validation would false-accept a bad key, deferring the
failure to the first chat 401. New spec field key_validation_path lets a
public-listing platform validate against an auth-requiring endpoint;
OpenRouter uses /key (401s for bad keys). Reusable for Vercel (also
public). Tests pin the /key validation and no regression to the default
/models path.

Gate caught a fixture regression: the kimi_import test used openrouter.ai
to represent a CUSTOM provider, which now correctly dedupes to the builtin
OpenRouter — moved the fixture to a reserved llm.example.test host that no
future platform can shadow.
2026-07-21 12:41:14 -04:00
ZacharyZhang-NY 7a2cd8a726 Add Google Gemini platform via OpenAI-compat endpoint (provider 7)
The 10th registry row: id "google", GEMINI_API_KEY > auth.json "google"
scope, https://generativelanguage.googleapis.com/v1beta/openai (Gemini's
OpenAI-compatibility shim) with KIGI_GOOGLE_BASE_URL override, Bearer,
OpenAI listing + ChatCompletions + Passthrough, enrichment-backed
metadata (models_dev_id google), tool-calling listing restriction.

Review caught a ship-blocking defect: Gemini's compat /models returns
-PREFIXED ids (confirmed via Google's own cookbook), but the
models.dev snapshot keys and the chat endpoint use the BARE id. Without
normalization the enrichment lookup misses and restrict_to_enriched
silently empties the Gemini catalog (login works, zero models
selectable). A doc WebFetch had hidden this — the compat docs show bare
INPUT ids to retrieve/chat but never print the list OUTPUT.

Fix: new spec field strip_listing_id_prefix, applied in the fetch before
filter/enrich/keying. Google sets Some("models/") (defensive: a no-op if
an id is already bare, so correct regardless of the live shape); all other
rows None. The e2e now feeds the REAL prefixed listing ids and asserts
they survive as the bare managed key google/gemini-2.5-pro with the bare
id on the wire (chat rejects the prefix); a registry test pins the config.

Note: models.dev models Gemini reasoning as budget_tokens (not
effort-type), so no auto effort-menu — models still fully work; reasoning
is dynamic. Gemini compat applies default safety filters (no BLOCK_NONE).
2026-07-21 11:47:47 -04:00
ZacharyZhang-NY e373ff8b29 Add Fireworks AI platform (provider 6)
The 9th registry row, pure Groq pattern: id "fireworks",
FIREWORKS_API_KEY > auth.json "fireworks" scope,
https://api.fireworks.ai/inference/v1 (note /inference/v1) with
KIGI_FIREWORKS_BASE_URL override, OpenAI listing + ChatCompletions +
Passthrough dialect, enrichment-backed metadata (models_dev_id
fireworks-ai) with the tool-calling listing restriction.

Fireworks native ids are deeply slashed (accounts/fireworks/models/glm-5p2);
the e2e pins the full round-trip: the managed key
(fireworks/accounts/fireworks/models/glm-5p2) parses back on the first
slash, and — the 404-risk property — the NATIVE id rides the inference
wire (entry.model and the resolved SamplerConfig.model) while the
fireworks/ prefix stays internal routing only.

Review (models.dev provider.toml + Fireworks docs): row facts confirmed,
registry integrity at 9, counts complete, e2e strong on all axes, zero
defects. One tradeoff logged as debt: restrict_to_enriched drops
fine-tuned/account-scoped deployed models (a headline Fireworks feature)
that can never be in models.dev.
2026-07-21 11:03:50 -04:00
ZacharyZhang-NY 9953a26b8d Add Mistral platform + Mistral dialect + array-content handling (provider 5)
The 8th registry row: id "mistral", MISTRAL_API_KEY > auth.json
"mistral" scope, https://api.mistral.ai/v1 with KIGI_MISTRAL_BASE_URL
override, enrichment-backed metadata with the tool-calling listing
restriction (embed/moderation/OCR noise).

Mistral is NOT a pure-pattern provider — an adversarial review found two
doc-confirmed blockers that no test exercises (no e2e covers a chat POST),
so the gate-green registry row alone would have shipped it DOA. A research
workflow pinned the exact wire shapes against the mistralai/client-python
SDK source (adversarially verified), then both were fixed:

1. stream_options 422: Mistral's strict Pydantic validator rejects the
   stream_options.include_usage field kigi injects on every streaming
   request (the SDK's request model has no such field). New
   ChatCompat::Mistral dialect strips it (plus the kigi-private message
   fields, like Passthrough). Streaming usage falls back to token
   estimation.
2. Reasoning content arrays: Mistral reasoning models return content as
   Union[str, List[ContentChunk]] on both streaming and non-streaming,
   which the flat Option<String> path could not decode -> aborted turn.
   A UNIVERSAL lenient deserializer (#[serde(from = "Raw..")] on
   ChatResponseMessage + ChatChunkDelta) accepts string-or-array, routing
   {type:text} chunks to the answer and the nested text of {type:thinking}
   chunks to reasoning_content, tolerant of the OPEN chunk union (unknown
   types ignored, never fatal). String content stays byte-identical for
   every other provider (kimi/deepseek/groq/BYOK).

Review refuted all seven attack lines (no regression, no crash, exhaustive)
and flagged one coverage gap, now closed: a stream-consumer integration
test drives a full thinking -> transition -> answer chunk sequence and
proves it yields the same reasoning-sibling + assistant-answer result as
the reasoning_content string path.

Also folds a verified quirk matrix for all 23 remaining API providers into
providers-plan.md, tiered by real difficulty (self-enriching OpenRouter/
Vercel; bare-array Together listing; Messages-dialect MiniMax reusing the
Anthropic machinery; non-Bearer Azure/Bedrock; router wildcards; the OAuth
block).
2026-07-21 10:36:33 -04:00
ZacharyZhang-NY 49e6414c29 Add Groq platform (provider 4)
The 7th registry row and the first pure-pattern cycle: id "groq",
GROQ_API_KEY > auth.json "groq" scope, https://api.groq.com/openai/v1
with KIGI_GROQ_BASE_URL override, OpenAI listing + ChatCompletions +
Passthrough dialect (Groq accepts the OpenAI-style reasoning_effort
scalar verbatim), enrichment-backed metadata with the tool-calling
restriction (the listing carries whisper/tts/guard noise — 8 of 15
enrichment entries are non-chat).

Review verdict: faithful pattern repeat, zero blocking findings — row
facts verified against live Groq docs, registry integrity at 7, e2e
proven strong on both axes (restriction + dialect mapping). Added the
recommended pin: managed keys split on the FIRST slash, so Groq's
provider-native slashed ids (openai/gpt-oss-120b and 10 more) round-trip
as groq/openai/gpt-oss-120b → (Groq, openai/gpt-oss-120b).

Also fixes a pre-existing test race this cycle surfaced: the enterprise-
endpoints test asserted moonshot's fixed base while non-serial, racing
serial tests that legitimately point KIGI_MOONSHOT_CN_BASE_URL at
wiremock; now serial + env-unset like its documented siblings. Two
display-only advisories logged as tracked debt (Groq's delta.reasoning
field invisible in the TUI; platform-generic rate-limit copy).
2026-07-21 08:08:43 -04:00
ZacharyZhang-NY 7efb4b07cc Add DeepSeek platform + ChatCompletions dialect system (provider 3)
The 6th registry row: id "deepseek", DEEPSEEK_API_KEY > auth.json
"deepseek" scope, base https://api.deepseek.com (chat rides
{base}/chat/completions per official docs) with KIGI_DEEPSEEK_BASE_URL
override, enrichment-backed metadata (1M context, 384k output cap,
high/max effort menu).

Structural fix the cycle exposed: kigi's Kimi-specific body adaptation
ran UNCONDITIONALLY on every ChatCompletions request. New ChatCompat
dialect, declared per platform row and threaded through SamplerConfig,
ClientDefaults, and the session-persisted SamplingConfig (serde-default
Kimi keeps restored pre-field sessions and BYOK endpoints byte-identical;
production persist seams copy it; subagents inherit it):
- Kimi: full legacy pipeline (dispatch ≡ legacy pinned)
- DeepSeek: thinking:{type, reasoning_effort} per api-docs.deepseek.com
  (server maps low/medium→high, xhigh→max itself; none disables; absent
  leaves the server default)
- Passthrough: OpenAI-style reasoning_effort scalar untouched (unblocks
  Groq and the rest of the OpenAI-compatible list)

Review-confirmed release blocker fixed: kigi replays Kimi's
reasoning_content (and its private model_id) on input assistant
messages — Kimi consumes these, but DeepSeek documents input
reasoning_content as prefix-mode-only (historically a 400) and other
providers don't know either field. The DeepSeek and Passthrough arms now
strip both; Kimi's own pipeline is untouched. Pinned on both message
shapes.
2026-07-21 07:21:05 -04:00
ZacharyZhang-NY b86722f508 Add Anthropic platform: wire-served metadata via listing dialect (provider 2)
The 5th registry row: id "anthropic", ANTHROPIC_API_KEY > auth.json
"anthropic" scope, api.anthropic.com/v1 with KIGI_ANTHROPIC_BASE_URL
override, Messages dialect. Two new spec dimensions most future rows
reuse: ListingDialect (Anthropic's /v1/models wants x-api-key +
anthropic-version headers, ?limit=1000, and its own response shape) and
PlatformKeyHeader (Bearer vs x-api-key across listing/validation/
inference, with auth_scheme stamped onto entries).

The 2026 Anthropic listing serves real metadata: the adapter maps
max_input_tokens, per-level effort capabilities (low..max as the menu,
xhigh/max distinct), thinking/image flags — and enrichment fills only
genuine wire gaps (e2e pins wire-1M beating enrichment, and a zero
context filled to 200k).

Two review-confirmed defects fixed red-green:
- Output caps were dropped at three layers, so every sub-128K-output
  model (64k Haiku, legacy models) would 400 on EVERY request against
  the sampler's 128K max_tokens default. Wire max_tokens and enrichment
  limit.output now flow to entry.max_completion_tokens.
- An explicit wire effort-decline was indistinguishable from wire
  silence, letting enrichment inject effort menus pre-4.6 models reject
  (adaptive thinking 400). The adapter now emits a decline sentinel
  (support:false) that enrichment respects — proven end to end.

Also: the Messages client now sends anthropic-version (previously never
sent — real api.anthropic.com rejects such requests; pinned across all
three scheme/backend quadrants), key validation builds per-key-header
requests, missing listing data fails fast, empty-id ghosts drop with a
warning, kimi-import recognizes api.anthropic.com as built-in
automatically.
2026-07-21 06:12:53 -04:00
ZacharyZhang-NY 23e94939c0 Add OpenAI platform: live model fetching with enrichment (provider 1)
The 4th registry row: id "openai", OPENAI_API_KEY env > auth.json
"openai" scope (login picker/paste/validation all registry-generic —
zero TUI changes needed, pinned by the picker test), base
https://api.openai.com/v1 with KIGI_OPENAI_BASE_URL override, Responses
dialect via the new PlatformWireApi spec field, enrichment-backed
metadata (wire_serves_metadata=false).

OpenAI's GET /v1/models returns bare ids and is polluted with
tts/whisper/embeddings entries: the listing is restricted to
enrichment-known TOOL-CALLING models (review caught that membership
alone admitted models.dev-known embeddings models, which would 400 on
every agentic request; dropped ids are debug-logged for launch-day
diagnosability). Context windows, effort menus, display names, and
thinking capability come from the enrichment pipeline — wiremock e2e
pins the full contract: polluted live listing + models.dev →
one Responses-backed chat model with a 400k documented context window.

Responses max-effort wiring (closes the P0c-1 debt): canonical effort
rides a CreateResponseWrapper sidecar and patch_reasoning_effort writes
it onto the serialized body at both send sites (all seven levels pinned,
xhigh/max distinct, summary preserved); normalize_effort_echo drops
echoes async-openai's typed enum cannot represent at both the non-stream
and SSE parse seams; the dead typed to_responses_api converter is
deleted. Kimi/moonshot stay byte-identical (ChatCompletions untouched,
wire_api maps to the same default; kimi wire tests green).

kimi-import now recognizes ANY registry platform host as built-in
(was hardcoded moonshot), covering openai and future rows.
2026-07-21 05:04:55 -04:00
ZacharyZhang-NY fdf9b956f5 Add models.dev metadata-enrichment pipeline (providers P0c-2)
Provider /models listings that return bare ids (OpenAI-style) get context
windows, thinking levels, image support, and display names from models.dev:
kigi-models owns the transform (parse_api_json — ONE field interpretation
for the bundled snapshot AND runtime refreshes), enrich_wire_model fills
gaps with wire values always winning and model availability strictly
wire-truth. Spec rows gained models_dev_id + wire_serves_metadata; all
three current platforms are wire-served, so this pipeline is provably
inert for them (byte-identical catalogs, zero egress, zero ~/.kigi writes
— adversarially verified).

Shell side: enrichment_fetch with a 24h disk cache guarded by binary
version + keep-set + future-stamp sanity (a registry change or downgrade
refetches instead of serving a catalog missing new providers), refresh of
https://models.dev/api.json filtered to registry ids, KIGI_MODELS_DEV_URL
override with case/whitespace-tolerant kill switch, fallback chain
fresh-cache > refresh > stale-cache > bundled (each step logged). The
fast path returns an empty catalog without forcing the bundled parse.

From the review: blast-radius-confined parsing (one drifted provider on
models.dev warn-skips instead of failing the whole refresh), registry-
coverage and field-coverage tests guarding script/parser drift, a path-
injectable core with 8 state-machine tests (one of which caught a guard
patch that had failed to apply), _meta provenance stamp in the snapshot,
and models.dev (MIT) attribution in NOTICE. Snapshot: 29 providers, 1124
models, 246KB, regenerated by scripts/gen_enrichment_snapshot.py (pure
filter, no transform).
2026-07-21 03:36:51 -04:00
ZacharyZhang-NY c5ddaec71e Add per-provider auth.json keys; make auth methods registry-generic (P0b)
Platform API keys now live in auth.json under the platform-id scope (the
per-provider auth.json key contract), resolved env > auth.json > legacy
[platforms.*] config.toml (read-only fallback). The TUI login picker,
paste box, auth-method advertising, and authenticate handler are all
registry-generic: a new PlatformSpec row appears in the login UI and
authenticates with zero UI changes. Spec rows gained vendor/console_host/
login_label display fields (moonshot strings byte-identical, pinned by
tests).

Adversarial review caught that auth.json keys were validated at login but
never stamped onto catalog entries (completions would 401; restart lost
eager auth). Fixed red-green: resolve_model_list/resolve_model_catalog now
take a resolved PlatformApiKeys snapshot consumed by the credential-
stamping layer (auth.json beats stale config.toml, matching the login
validator), with production callers resolving fresh per catalog build.
Also from review: the new auth.json writer takes the manager's cross-
process flock (bounded retry — an unlocked RMW racing a token refresh
could revert a rotated refresh token); the oauth-401 wiremock test is
hermetic (KIGI_SHARE_DIR tempdir; it could read a dev's real auth.json
and hit live moonshot); cli_models resolves real keys; auth.json is read
once per registry sweep; caller-less lock_config_writes deleted; catalog
resolvers tightened to pub(crate); stale config.toml doc comments and the
no-credentials error copy updated.
2026-07-21 01:18:46 -04:00
ZacharyZhang-NY 99d99fb47a Refactor platform registry onto spec rows (providers P0a)
Replace PlatformId's per-method match arms with static PlatformSpec rows
read through a single spec() accessor. Zero behavior change (adversarially
reviewed: ids, display names, base URLs + env override pairing, oauth flag,
prefix filters, and api-key env precedence order are byte-identical; all
public signatures unchanged). Groundwork for the multi-provider expansion:
adding a platform becomes variant + ALL entry + spec() arm + row.

Guards added for the row-addition workflow: spec-id uniqueness test (parse
scans rows) and a compile-forced ALL-completeness test (a variant missing
from ALL would otherwise be silently unparseable and skipped by model
sync). Future-proofed two assertions that hardcoded 'openai' as an
unknown-platform example.
2026-07-21 00:03:46 -04:00
ZacharyZhang-NY 6f31415ed6 §9 acceptance: grep-zero sweep — every internal x.ai/grok identifier renamed
The PRD's first acceptance gate now holds: grep -RinE '\bx\.ai\b|grok'
crates/ --include='*.rs' → 0 matches (exempt: NOTICE and third-party
license archives, README provenance, and the required 'Based on Grok
Build Open Source' attribution, now sourced from version_attribution.txt).

Wire-visible renames (both sides in this repo, changed in lockstep):
- Auth method id 'grok.com' → 'kimi-code' (AuthMethodKind::KimiCode).
- Every x.ai/* and _x.ai/* ACP ext method and meta key → kigi/* /
  _kigi/* (~200 names; grokShell → kigiShell). Session-file replay keeps
  a read-side alias for the legacy '_x.ai/session/update' method so
  existing updates.jsonl histories load; writes emit only the new name
  (both directions test-pinned).
- Agent types grok-build* → kigi* with a documented legacy-prefix alias
  at resolution time so persisted sessions keep resolving.
- ToolNamespace/BuiltinAgentName GrokBuild* → Kigi* (wire snake_case
  kigi/kigi_concise/kigi_hashline; schema regenerated); grok_build
  implementation dirs renamed to kigi*.
- x-grok-* headers → x-kigi-*, __GROK_* sentinels → __KIGI_*, themes
  grokday/groknight → kigiday/kiginight (old persisted values fall back
  to the default theme), web_fetch allowlist xAI hosts → kimi.com +
  moonshot platforms, changelog CDN → this repo, grok-build changelog
  archives deleted.
- BYOK default endpoint removed: [endpoints] api_base_url is now truly
  optional with NO default — consumers fail fast with the flag name when
  unset (no silent x.ai egress). Mock harnesses inject it explicitly.
- System-prompt identity fixed: 'released by xAI' → 'an unofficial
  community CLI for Kimi' (template + regenerated encrypted form).

Also repaired pre-existing grok-era test debt found by the sweep: the
stale trace_classify default-model pin, the grok-pager UA label test,
pty-harness stale-binary reuse and non-hermetic moonshot routing (a PTY
test could previously reach the real api.moonshot.cn), and the outdated
oauth fixture scope key.

Gates: §9 grep 0; fmt clean; workspace check/clippy 0/0 (-D warnings);
FULL cargo test --workspace: 234 suites, 21,961 passed, 0 failed;
deny advisories ok.
2026-07-18 02:48:46 -04:00
ZacharyZhang-NY 74b210535e F5: web search/fetch on the Kimi services (kimi-cli parity)
web_search now speaks the Kimi search service (kimi-cli tools/web/search.py,
wire-verified against api.kimi.com):
- POST {coding_base}/search with {text_query, limit 1-20 (default 5),
  enable_page_crawling, timeout_seconds: 30}, OAuth bearer +
  X-Msh-Tool-Call-Id; results render in kimi-cli's Title/Date/URL/Summary
  schema with result URLs as citations.
- The old implementation called the xAI Responses API with a search model;
  that client is fully replaced and the entire model-based config surface
  is excised root-and-branch: web_search_model config keys/env/CLI plumbing,
  resolve_web_search_sampling_config, toolset web_search SamplerConfig,
  RemoteSettings.web_search_model, default_web_search_model.
- Enablement is now purely structural: the service exists only on the Kimi
  Code subscription channel, so OAuth sessions get Enabled and API-key-only
  sessions get Disabled (tool absent) — per PRD F5.

web_fetch gains the Kimi fetch service as its primary path (kimi-cli
tools/web/fetch.py): POST {coding_base}/fetch with {url}, Accept:
text/markdown, OAuth bearer + X-Msh-Tool-Call-Id; the 200 body is the
extracted markdown (still overflow-budgeted). Any service failure falls
back to the existing local pipeline (SSRF guards, cache, extraction).
The tool gate defaults ON now (kimi-cli always offers FetchURL) and the
egress User-Agent no longer claims grok-agent/x.ai.

Verified end-to-end against the scripted mock service: a headless session
drove web_search (limit/crawling/call-id observed on the wire) then
web_fetch (Accept + call-id observed) to completion.
2026-07-17 19:33:19 -04:00
ZacharyZhang-NY 5f3f9509b9 Wire K3 thinking-effort levels end to end (fixes 'model does not support reasoning effort')
The live /models wire (verified against api.kimi.com) marks every Kimi
Code model supports_thinking_type: "only" and gives K3 a think_efforts
block {support, valid_efforts: [low, high, max], default_effort: max} —
both of which the F4 sync discarded, hardcoding
supports_reasoning_effort: false. Every effort selection was therefore
rejected with 'current model does not support reasoning effort'.

- kigi-models: WireModel gains supports_thinking_type + WireThinkEfforts;
  "only" forces the always_thinking capability.
- models_fetch: think_efforts maps into the catalog entry — wire tokens
  stay the option ids/labels (max/Max), canonical values map via the
  ReasoningEffort parser (max → Xhigh), default_effort marks the default.
  This lights up the existing /model <model> [effort] two-phase completion
  and the /effort menu with the server's own vocabulary.
- kimi_compat: the effort level rides the wire as thinking.effort
  ({"type": "enabled", "effort": "low"} is accepted live; invalid
  levels are a 400). Only the canonical-vs-wire spelling divergence
  (xhigh → max) is renamed; levels pass through verbatim so a contract
  violation surfaces instead of being clamped away.

Live acceptance: kigi -m kimi-code/k3 --reasoning-effort max -p ... round
trips against api.kimi.com, and the refreshed models_cache.json carries
low/high/max with the max default.
2026-07-17 18:22:00 -04:00
ZacharyZhang-NY fe1f885bb3 M1/F2+F4: platform registry, Moonshot API-key channel, dynamic model sync
F2 — fixed three-platform registry in kigi-models: kimi-code
(subscription, OAuth bearer, base kigi_env::coding_api_base_url()),
moonshot-cn (https://api.moonshot.cn/v1), moonshot-ai
(https://api.moonshot.ai/v1) with kimi-k model-prefix filtering.
Moonshot API keys via KIGI_MOONSHOT_CN_API_KEY / KIGI_MOONSHOT_AI_API_KEY
(+ KIGI_MOONSHOT_API_KEY shared fallback) or ~/.kigi/config.toml;
values redacted from logs/display.

F4 — model catalog now syncs from GET {base}/models (Bearer auth,
wire shape per official kimi-cli: id/context_length/supports_reasoning/
supports_image_in/supports_video_in/display_name) with the official
capability-derivation rules (thinking / always_thinking-in-name /
kimi-k2 implicit set). Managed keys {platform_id}/{model_id}; default
model = first list entry; default thinking iff capabilities contain
thinking/always_thinking. Sync failure → last cache; no cache →
built-in fallback table seeded from ids sourced in official kimi-cli
(kimi-for-coding, kimi-k2-turbo-preview, kimi-k2-thinking-turbo).
401 during sync forces one token refresh and retries.

Model resolution priority preserved: CLI > env > config > server >
fallback. Grok model artifacts (grok-4*/grok-build catalog, tier
gating remnants) removed from non-test code.

All first-party endpoints re-verified live: device_authorization mints
real codes; /models on all three platforms answers with real API auth
errors when unauthenticated.

Gates: check/clippy --all-targets 0/0, fmt clean, deny ok,
kigi-shell lib 5136 green, kigi-tui lib 6819 green, kigi-models 8.
2026-07-17 09:23:44 -04:00
ZacharyZhang-NY d6c20fc13f M0: compilable skeleton — Kigi 0.1.0 fork surgery
Hard fork of xai-org/grok-build (Apache-2.0) re-targeted as Kigi, an
unofficial Kimi Code CLI community build.

Rename & identity
- 72 xai-*/xai-grok-* crates -> kigi-* (explicit: xai-grok-pager-bin ->
  kigi-bin [binary `kigi`], xai-grok-pager -> kigi-tui; rest mechanical);
  ptyctl, ptyctl-cli, third_party/ unchanged; proto package
  xai.grok.tools.v1 -> kigi.tools.v1
- Config home ~/.kigi (KIGI_SHARE_DIR override), env prefix GROK_* ->
  KIGI_*, `kigi --version` carries the unofficial-community-build notice
- clap identity, help text, startup banner, prompt templates rebranded
  (templates re-encrypted)

Deletions (PRD removal list #5/#6/#7/#9/#10)
- voice input (xai-grok-voice) and all TUI wiring
- telemetry: Mixpanel client, external OTel stream, Sentry, OTLP layers,
  trace/GCS/S3 upload queues (kigi-file-utils halved), workspace upload
  module & dc_log, heap-profile uploader, auth-diagnostics uploader,
  session-analytics halves of feedback; local zero-egress observability
  preserved in new kigi-log crate (unified log, --debug firehose,
  subsystem file logs, opt-in instrumentation)
- announcements (crate, remote-settings fields, TUI surfaces)
- plugin marketplace (crate, sources/browse/CTA/extensions-modal tab);
  direct plugin install/uninstall/update via kigi-agent git_install kept
- relay/gateway/assets endpoints and features (agent relay, headless
  relay transport, gateway bridge, LeaderEnvUrls); leader IPC socket now
  ~/.kigi/leader.sock + KIGI_LEADER_SOCKET, no ws-url derivation
- functional types rehomed instead of deleted: PermissionMode ->
  kigi-config-types, McpInitStrategy -> kigi-mcp, PrCreationSource ->
  session signals, TerminalDiagnostics -> kigi-pager-render, agent_id ->
  shell util

Endpoints
- kigi-env rewritten: single production KigiEndpoints {coding_api_base_url
  https://api.kimi.com/coding/v1 (KIGI_CODE_BASE_URL), oauth_host
  https://auth.kimi.com (KIGI_OAUTH_HOST), update_base_url (GitHub
  Releases API), upgrade_page_url}; GrokBuildEnvironment enum deleted

Toolchain & workspace hygiene
- Rust 1.97.0 pinned; edition 2024; full cargo update; git2 hoisted to
  workspace at 0.21 (Option->Result API migration), quick-xml 0.41
- Root Cargo.toml hand-maintained (PRD §8.1): version 0.1.0 inherited by
  all members, members sorted, unused deps pruned
- cargo-deny advisories gate (deny.toml with documented transitive
  exceptions); CI workflow (check/clippy/fmt/deny/test, macOS+Linux)
- cross-crate test seams re-gated behind `test-support` cargo feature;
  insta snapshot baselines renamed to the kigi_tui prefix
- clippy --workspace --all-targets: zero warnings; fmt clean

Fixes surfaced by the port
- updater probe/installer divergence (bin/kigi vs bin/grok symlink set)
- idle model-metadata refresh dead under KIGI_CODE_BASE_URL override
  (new is_effective_coding_endpoint_url, loopback+override aware)
- macOS symlinked-TMPDIR fixture canonicalization (foreign_sessions,
  fast-worktree); RSS measurement tests serialized via serial_test

Docs & legal (Apache §4)
- NOTICE added (upstream attribution + change statement); THIRD-PARTY
  notices sustained; kigi-tools ported-code notices extended; README,
  CONTRIBUTING, SECURITY, AGENTS.md rewritten

Out of scope for M0 (tracked): Kimi auth/inference (M1), search/fetch,
command parity, config import (M2), Computer Hub excision & final
brand-token sweep (M2), distribution & self-update rewrite (M3).
2026-07-17 05:31:01 -04:00