SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
13 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%

docs: v0.2.0 audit, README/CLAUDE for nine providers, OpenRouter ranking below native catalogs

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Simon-Pierre Boucher committed 16 days ago (Sep 8, 2026) parent 6771bf5

3 changed files +478 −10

modified docs/final-audit.md +20 −10
@@ -1,4 +1,4 @@
1 # PolyLLM — final production audit (v0.1.0, 2026-09-08)
1 +# PolyLLM — final production audit (v0.2.0, 2026-09-08)
2 2
3 3 ## Build
4 4 - `pnpm typecheck` — 0 errors. `pnpm lint` — 0 errors. `pnpm build` (on M3U96a, Next 16 production build) — ok.
@@ -17,13 +17,22 @@
17 17 - Audit log per user (account, login, key added/replaced/removed, password/email events).
18 18
19 19 ## Providers (documentation re-audited 2026-09-08; live test matrix `docs/provider-test-matrix.md`)
20 | | OpenAI | Anthropic | Gemini | xAI |
21 | --- | --- | --- | --- | --- |
22 | Docs current | ✅ developers.openai.com (Responses) | ✅ platform.claude.com + Models API | ✅ ai.google.dev | ✅ docs.x.ai/developers |
23 | SDK current | `openai` 7.10.0 | `@anthropic-ai/sdk` 0.124.0 | `@google/genai` 2.21.0 | `openai` 7.10.0 (baseURL api.x.ai) |
24 | Models current | 65 listed (gpt-6-astra, gpt-5.6-*, gpt-5.5, 5.4…) + shutdown dates | 11 (Fable 5.1, Opus 5, Sonnet 5…) with live capabilities | 18 chat models (3.8/3.7/3.6/3.5 Flash, 3.1 Pro…) ; 2.5 gen marked deprecated (404 for new users) | 7 (Grok 4.6/4.5/4.3/4.20/Build) with live pricing |
25 | Basic generation / streaming / system / vision / tools / structured output / reasoning / usage / errors | ✅ all | ✅ all | ✅ all | ✅ all |
26 | Notes | sampling only at effort none; no seed/stop | adaptive thinking may produce 0 thinking tokens on easy prompts; Haiku uses budget | owner key is free tier: tiny per-model daily quotas, 3.1 Pro paid-only | invalid key = HTTP 400; penalties always rejected; web search via /v1/responses |
20 +Nine providers, each with a dated research report (`docs/provider-research/<id>.md`), real probes (`research/<id>/`) and an
21 +adapter whose parameter translation only sends what the model accepts. The live matrix is **all ✅ for 13 capabilities × 9
22 +providers** (auth, model list, text, streaming, system prompt, vision, tools, structured output, reasoning, token usage,
23 +invalid key, error normalization, long response).
24 +
25 +| Provider | SDK / endpoint | Models listed | Highlights verified |
26 +| --- | --- | --- | --- |
27 +| OpenAI | `openai` 7.10, Responses API | 65 (+ alias gpt-5.6) | encrypted reasoning replay, sampling only at effort none, no seed/stop |
28 +| Anthropic | `@anthropic-ai/sdk` 0.124, Messages API | 11 | live capabilities, adaptive thinking, Haiku budget mode |
29 +| Google Gemini | `@google/genai` 2.21 | 18 chat | thinkingLevel/budget per family, thought signatures, free-tier quotas |
30 +| xAI | `openai` @ api.x.ai | 7 | reasoning_content, live pricing, 400 on bad key |
31 +| Mistral AI | `openai` @ api.mistral.ai (translated bodies) | 28 ids / 10 billing models | listing capabilities, `reasoning_effort` none/high, thinking content arrays, `document_url` PDFs, 422 strictness |
32 +| DeepSeek | `openai` @ api.deepseek.com | 3 | thinking on by default, `reasoning_effort` low/high/max, json_object only, cache-hit tokens |
33 +| Kimi (Moonshot AI) | `openai` @ api.moonshot.ai | 4 | `/models/{id}` capabilities, fixed temperature/top_p, reasoning_content replay |
34 +| OpenRouter | `openai` @ openrouter.ai | 323 after filters | `supported_parameters` gating, `usage.cost`, reasoning effort/budget, web plugin, upstream provider surfaced |
35 +| Cerebras | `openai` @ api.cerebras.ai | 3 (gemma retired) | strict validation whitelist, `reasoning_effort`, tools+response_format rule, strict schema |
27 36
28 37 ## Email (Resend)
29 38 - Sender: `PolyLLM <polyllm@mail.spboucher.ai>` (the key is not authorized for `spboucher.ai` or `polyllm.io`; `mail.spboucher.ai` verified — probed).
@@ -36,13 +45,14 @@
36 45 ## Production
37 46 - Node M3U96a, PM2 `polyllm-web` (Next, :8240) + `polyllm-ngrok` (`www.polyllm.io`), registry entry in mld.
38 47 - `https://www.polyllm.io/api/health/ready``{"status":"ok","database":"ok"}`; TLS via ngrok; HSTS.
39 - Startup model sync on the node: 65 + 11 + 18 + 7 models.
48 +- Startup model sync on the node: 65 + 11 + 18 + 7 + 28 + 3 + 4 + 323 + 3 models (nine providers).
40 49 - Production login tested and a real provider request streamed through ngrok (`e2e/prod-smoke.spec.ts`, account `spbou4+polyllm-test@icloud.com`).
41 50 - Backups: `deploy/backup/install.sh` (daily pg_dump 03:45, 14-day retention) — to run on the node.
42 51
43 52 ## Known limitations (honest)
44 53 - Built-in tools are limited to calculator / clock / random (side-effect free); provider-native web search and code execution are wired where supported.
45 54 - Audio input/output and image generation are catalogued as capabilities but not exposed in the chat UI (text, images, PDF, text files only).
46 - Gemini web-search grounding and code execution were not live-tested (free-tier quota); Anthropic/OpenAI/xAI web search paths are implemented from the audited docs and covered by the research probes, not by the matrix.
55 +- Gemini web-search grounding and code execution were not live-tested (free-tier quota); Anthropic/OpenAI/xAI/OpenRouter web search paths are implemented from the audited docs and covered by the research probes, not by the matrix. Kimi `$web_search` and Mistral Conversations-API tools are not exposed (round trips not stable / different API).
56 +- OpenRouter `reasoning_details` signatures are not replayed yet (works without; docs recommend replay).
47 57 - Conversation search is `ILIKE`-based (adequate for personal volumes); no full-text index yet.
48 58 - English only (architecture ready for locales).
added docs/provider-research/kimi.md +256 −0
@@ -0,0 +1,256 @@
1 +# Kimi (Moonshot AI, international platform) — provider research for PolyLLM
2 +
3 +Last documentation audit: **2026-09-08**
4 +Probes executed 2026-09-08 with a real key against `https://api.moonshot.ai/v1` (scripts in `research/kimi/`, raw outputs in `research/kimi/out/`). Everything marked **(probed)** was observed live; everything marked **(docs)** comes from the pages listed at the end.
5 +
6 +> Heads-up: every `https://platform.moonshot.ai/docs/...` URL in the brief now **301-redirects to `https://platform.kimi.ai/docs/...`** (the platform was rebranded "Kimi Open Platform"). The **API host did not move**: `https://api.moonshot.ai/v1` is the only documented base URL (`api.kimi.ai` → 404, `api.moonshot.cn` is the separate China platform whose keys are not interchangeable). The full doc index is `https://platform.kimi.ai/docs/llms.txt`; the sitemap.xml only lists two marketing pages.
7 +
8 +---
9 +
10 +## 1. Base URL, auth, headers
11 +
12 +| Item | Value |
13 +|---|---|
14 +| REST base URL | `https://api.moonshot.ai/v1` (OpenAI Chat Completions + Responses). Anthropic-compatible Messages API at `https://api.moonshot.ai/anthropic` (`/v1/messages`, kimi-k3 only) (docs) |
15 +| Auth | `Authorization: Bearer <KIMI_API_KEY>` |
16 +| Content type | `application/json`. Malformed field → **400** `{"error":{"message":"Invalid request: the \`messages\` field in the request (expected type []object) is illegal, and string is not acceptable","type":"invalid_request_error"}}` **(probed)** |
17 +| Optional request header | `X-Msh-Request-Nonce` (UUID v4) → response headers `Msh-Request-Timestamp` + `Msh-Request-Signature` for signature verification (docs) |
18 +| Response headers **(probed)** | `msh-request-id`, `x-msh-trace-id`, `msh-gid` (account tier, e.g. `enterprise-tier-2`), `msh-org-id`, `msh-project-id`, `msh-uid`, `msh-context-cache-token-saved: <n>` (cache hit size), `msh-context-cache-token-boosted-nvme`. **No `x-ratelimit-*` and no `retry-after` headers**, even on 429. Behind Cloudflare (`cf-ray`). |
19 +| Key validation | `GET /v1/models` (200 with a valid key, **401** `invalid_authentication_error` with a bad one) or `GET /v1/users/me/balance``{"code":0,"data":{"available_balance":54.78,"voucher_balance":4.78,"cash_balance":50},"scode":"0x0","status":true}` **(probed)** — cheap "validate key" + balance display. |
20 +| Token counting | `POST /v1/tokenizers/estimate-token-count` `{model, messages}``{"code":0,"data":{"total_tokens":93},...}` **(probed)**; accepts image/video parts (docs). |
21 +
22 +## 2. SDK recommendation (TypeScript / Node)
23 +
24 +- **No official Moonshot/Kimi JS SDK** (npm `moonshot`, `kimi-ai`, `@moonshot-ai/sdk`, `@moonshotai/sdk`, `kimi-sdk`, `moonshotai`, `@ai-sdk/moonshot`, `@ai-sdk/kimi` do not exist / are unrelated — checked 2026-09-08). Docs recommend the **official OpenAI SDK (Python/Node)** with `base_url` swapped, or the Anthropic SDK against `/anthropic`. All doc samples are curl + Python; no JS samples.
25 +- **Recommendation for PolyLLM:** OpenAI SDK **`openai@7.10.0`** with `baseURL: "https://api.moonshot.ai/v1"`, `maxRetries: 0` (the SDK's default 2 retries count against the 100 RPM quota — docs warn about this), long `timeout`. Kimi-specific fields (`thinking`, `reasoning_effort` non-OpenAI values, `partial`) must be passed as extra body fields (cast). Smoke test **(probed)**: `client.chat.completions.create({..., stream:true, thinking:{type:"enabled"}} as any)` streams `delta.reasoning_content` + tool_calls fine.
26 +
27 +## 3. Endpoints
28 +
29 +| Endpoint | Status | Notes |
30 +|---|---|---|
31 +| `POST /v1/chat/completions` | **primary** | OpenAI-compatible; all 4 models; `reasoning_content`; builtin `$web_search`. **(probed)** |
32 +| `POST /v1/responses` | active | Docs: kimi-k3 only — **kimi-k2.6 also answered 200 (probed)**. Stateless (`store`/`previous_response_id`/`conversation` always false/null, docs). Server-side `web_search` tool. SSE **with `event:` lines**. `usage.input_tokens_details.cache_write_tokens`. **(probed)** |
33 +| `POST /anthropic/v1/messages` | active | Anthropic SDK compatible, kimi-k3 only, `output_tokens_details.thinking_tokens` (docs; not probed) |
34 +| `GET /v1/models`, `GET /v1/models/{id}` | active | Rich flags, see §12. **(probed)** |
35 +| `GET /v1/users/me/balance` | active | §1 **(probed)** |
36 +| `POST /v1/tokenizers/estimate-token-count` | active | §1 **(probed)** |
37 +| `POST/GET/DELETE /v1/files`, `/v1/files/{id}/content` | active | purposes `file-extract`, `image`, `video`, `batch`; 100 MB/file, 1 000 files, 10 GB; extraction "temporarily free"; new ids `file_…` since 2026-08-31; images/videos referenced as `ms://<file_id>` (docs) |
38 +| `GET /v1/formulas`, `GET /v1/formulas/{ns}/{name}[:latest]/tools`, `POST /v1/formulas/{uri}/fibers` | active | "Official tools" (web-search, fetch, convert, code-runner, quickjs, excel, memory, …). **(probed)** §11 |
39 +| `POST /v1/batch/*` | active | 60 % of list price (docs) |
40 +
41 +## 4. Chat Completions request/response (probed shapes)
42 +
43 +Request fields (docs API ref + OpenAPI + probes): `model`, `messages`, `max_completion_tokens` (default 131 072 on K3 / 32 768 on K2.x, max 1 048 576 on K3) — **`max_tokens` accepted as alias (probed)**, `stream`, `stream_options.include_usage`, `stop` (≤ 5 strings, ≤ 32 bytes each), `response_format` (`text|json_object|json_schema`), `tools`, `tool_choice`, `parallel_tool_calls`, `thinking` (`{type:"enabled"|"disabled", keep?: "all"|null}`), `reasoning_effort` (`low|high|max`, K3), `logprobs`/`top_logprobs` (0–20), `prediction` (`{type:"content", content}`), `prompt_cache_key`, `safety_identifier`, `partial` (on the last assistant message), `temperature`/`top_p`/`n`/`presence_penalty`/`frequency_penalty` (**all fixed**, §9), `seed` (accepted, undocumented).
44 +
45 +Roles: `system`, `user`, `assistant`, `tool`. **`developer` → 400 `Invalid request: role 'developer' is not allowed`** on all 4 models **(probed)** — map developer→system in the adapter. Unknown top-level params (`foo_bar`) are silently ignored **(probed)**. Tool messages: `{role:"tool", tool_call_id, name, content}` (docs include `name`; K3 builtin round trip failed without it — see §11).
46 +
47 +Non-streaming response **(probed, kimi-k2.6)**:
48 +
49 +```json
50 +{ "id": "chatcmpl-6a9fabafa11938d17f75e852", "object": "chat.completion", "created": 1788849073, "model": "kimi-k2.6",
51 + "choices": [{ "index": 0, "finish_reason": "stop",
52 + "message": { "role": "assistant", "content": "Two plus two equals four.",
53 + "reasoning_content": "The user is asking a simple math question…" } }],
54 + "usage": { "prompt_tokens": 20, "completion_tokens": 78, "total_tokens": 98,
55 + "completion_tokens_details": { "reasoning_tokens": 70 } } }
56 +```
57 +
58 +- **`completion_tokens` INCLUDES reasoning tokens** (78 = 70 reasoning + 8 visible); `total_tokens = prompt + completion`. Bill reasoning at the output price.
59 +- On a cache hit usage gains **`cached_tokens` at the top level AND `prompt_tokens_details.cached_tokens`** (same value) **(probed)**. No `system_fingerprint` in non-streamed responses (present in every stream chunk, e.g. `fpv0_7f4e9b4b`).
60 +- `finish_reason`: `stop`, `length`, `tool_calls` **(probed)**. **`max_tokens` caps reasoning + content together**: `max_tokens: 200` on K2.6/K3 → `finish_reason: "length"` with *empty* `content` after 199 reasoning tokens **(probed)**. Docs recommend `max_tokens ≥ 16000` for thinking models. **Adapter rule: never send a small max_tokens to a thinking model; treat `length` with empty content as "thinking truncated".**
61 +- Hidden system prompt: ~17 prompt tokens overhead on K2.x, **~92–98 on kimi-k3** (25 when thinking disabled) **(probed)**.
62 +- Non-thinking K2.6 responses still carry `reasoning_content: ""` when tools are called **(probed)**.
63 +
64 +## 5. Streaming protocol (chat completions) **(probed on all 4 models)**
65 +
66 +SSE `text/event-stream`, `data: {json}` lines only (no `event:` field), terminated by `data: [DONE]` (docs insist: end on `[DONE]`, not on `finish_reason`).
67 +
68 +```json
69 +data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":1788849079,"model":"kimi-k2.6",
70 + "choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}],"system_fingerprint":"fpv0_7f4e9b4b"}
71 +data: {"choices":[{"index":0,"delta":{"reasoning_content":"The"},"finish_reason":null}]}
72 +
73 +data: {"choices":[{"index":0,"delta":{"content":"Bon"},"finish_reason":null}]}
74 +data: {"choices":[{"index":0,"delta":{},"finish_reason":"stop",
75 + "usage":{"prompt_tokens":17,"completion_tokens":96,"total_tokens":113,"cached_tokens":17,
76 + "completion_tokens_details":{"reasoning_tokens":86},"prompt_tokens_details":{"cached_tokens":17}}}]}
77 +data: {"choices":[],"usage":{…same…}} ← only with stream_options.include_usage
78 +data: [DONE]
79 +```
80 +
81 +- Delta keys observed: `role`, `content`, `reasoning_content`, `tool_calls`. First chunk is always `{"role":"assistant","content":""}`; reasoning deltas precede content deltas; K3 may emit `content` text *before* `tool_calls` ("I'll check the weather…") **(probed)**.
82 +- **Usage is ALWAYS present inside the finish chunk's choice (`choices[0].usage`, non-standard)** even without `stream_options`; `include_usage: true` additionally appends the standard `choices: []` chunk with top-level `usage` **(probed)**. The OpenAI SDK only surfaces the latter → keep sending `include_usage: true`.
83 +- Tool-call deltas (OpenAI-standard fragmenting): first fragment `{"index":0,"id":"get_weather_0","type":"function","function":{"name":"get_weather","arguments":""}}`, then `{"index":0,"function":{"arguments":"{\""}}` … (6–8 fragments for one call) → concatenate by `index`, `finish_reason: "tool_calls"` **(probed)**. Tool-call ids are **`<function_name>_<index>`** (`get_weather_0`) — not unique across turns; builtin ids are `t-web_search-<hex>`.
84 +
85 +## 6. Tool / function calling **(probed on all 4 models)**
86 +
87 +- Format: OpenAI nested `{type:"function", function:{name, description, parameters}}`; names `[A-Za-z0-9_-]`, must be unique per request. Parallel calls possible (model returns several `tool_calls`); `parallel_tool_calls: true|false` accepted everywhere with no error **(probed)**.
88 +- `tool_choice`: `auto` (default), `none`, `required`, `{type:"function",function:{name}}`. **With thinking enabled**: `required` → 400 `tool_choice 'required' is incompatible with thinking enabled` on **K2.6 / K2.7-code / K2.7-code-highspeed** (allowed on **K3**); forcing a specific function → 400 `tool_choice 'specified' is incompatible with thinking enabled` on **all four** (works on K2.6 with `thinking.type: "disabled"`) **(probed)**.
89 +- Round trip (assistant message with `tool_calls` + `{role:"tool", tool_call_id, name, content}`) works on every model **(probed)**. Docs: forgetting the assistant message → "tool_call_id not found".
90 +- **`reasoning_content` replay**: docs say to always send back the complete assistant message (incl. `reasoning_content`) for thinking models; K2.7-code has *Preserved Thinking* always on, K2.6 only with `thinking.keep: "all"`. **Probed: omitting `reasoning_content`, or sending `""`, did NOT produce an error on any of the 4 models** — the round trip completed with 200 and a correct answer. So it is a quality requirement, not a protocol one. Adapter: store and replay `reasoning_content` on assistant messages with `tool_calls` (cheap: it is billed as prompt tokens), but do not fail if it is missing.
91 +- Dynamic tool loading (K3 only): `{role:"system", tools:[…]}` message *without* `content` injects tools mid-conversation; on other models → tokenization error (docs).
92 +
93 +## 7. Structured output **(probed)**
94 +
95 +- `response_format: {type:"json_object"}` → valid JSON object on all 4 models (mention JSON in the prompt; only JSON *objects*, never arrays — docs).
96 +- `response_format: {type:"json_schema", json_schema:{name, strict:true, schema}}` ("Moonshot Flavored JSON Schema" MFJS spec): valid, schema-conformant JSON on **K3, K2.7-code, K2.7-code-highspeed**. **kimi-k2.6 with thinking enabled ignored the schema and returned prose twice (probed, 3 000 max_tokens)**; with `thinking: disabled` it returned valid JSON preceded by ~300 blank lines. Docs themselves say K2.6 "occasionally behaves unstably with complex schemas". **Adapter: mark K2.6 structured output as unreliable / validate client-side; prefer K3 or K2.7-code.**
97 +- Thinking models still emit `reasoning_content`; parse only `message.content`. A too-small `max_tokens` truncates the JSON (`finish_reason: length`).
98 +
99 +## 8. Reasoning controls **(probed matrix)**
100 +
101 +| Model | Thinking | `thinking` param | `reasoning_effort` | `reasoning_content` |
102 +|---|---|---|---|---|
103 +| kimi-k3 | always on (docs); `/models`: `supports_thinking_type: "only"`, `think_efforts: low/high/max, default max` | `{type:"enabled"}` ✓, `{type:"enabled",keep:"all"}` ✓, **`{type:"disabled"}` → 200 with NO reasoning (undocumented, prompt overhead drops 92→25 tokens)** | `low`/`high`/`max` (docs, default `max`) ✓; **`medium` accepted**; **`none` → 200 with no reasoning (undocumented)** | yes (non-stream + `delta.reasoning_content`) |
104 +| kimi-k2.6 | on by default, switchable | `enabled` (default, temp 1.0) / `disabled` (temp 0.6) / `enabled+keep:"all"` ✓ | not documented for K2.6 but **accepted: `low|medium|high|max` (no visible effect), `none` → disables thinking** | yes; `""` when disabled |
105 +| kimi-k2.7-code | always on | only `{type:"enabled"[,keep:"all"]}`; **`disabled` → 400 `invalid thinking: only type=enabled is allowed for this model`** | `low|medium|high|max` accepted (no visible effect); **`none` → 400** (same message) | yes |
106 +| kimi-k2.7-code-highspeed | always on | same as k2.7-code | same as k2.7-code | yes |
107 +
108 +- `/v1/models` exposes `reasoning_efforts.valid_efforts` only for K3 — drive the UI from that. No thinking-budget parameter. Reasoning tokens are inside `completion_tokens` and billed as output. `thinking.keep: "all"` keeps historical reasoning in context (extra prompt tokens).
109 +- Responses API: `reasoning: {effort}``{type:"reasoning", summary:[{type:"summary_text", text}]}` output item (full text, not a summary) **(probed)**.
110 +
111 +## 9. Sampling & other parameters — support matrix **(probed, chat completions)**
112 +
113 +| Param | k3 | k2.7-code | k2.7-code-highspeed | k2.6 |
114 +|---|---|---|---|---|
115 +| `temperature` | **only `1`** (0, 0.5, 1.5, 2 → 400) | only `1` | only `1` | only `1` with thinking (docs: 0.6 fixed when disabled) |
116 +| `top_p` | **only `0.95`** | only `0.95` | only `0.95` | only `0.95` |
117 +| `n` | only `1` | only `1` | only `1` | only `1` |
118 +| `presence_penalty` / `frequency_penalty` | **only `0`** | only `0` | only `0` | only `0` |
119 +| `max_tokens` / `max_completion_tokens` | ✓ (both) | ✓ | ✓ | ✓ |
120 +| `stop` (≤ 5) | ✓ (6 → 400 `stop array too long…maximum length 5`) | ✓ | ✓ | ✓ |
121 +| `seed` | ✓ accepted (undocumented) | ✓ | ✓ | ✓ |
122 +| `logprobs`/`top_logprobs` | **400 `invalid logprobs: only false is allowed for this model`** | ✓ (logprobs returned) | **429 `engine_overloaded_error` on every attempt (8/8) — treat as unsupported** | ✓ |
123 +| `response_format` json_object / json_schema | ✓ / ✓ | ✓ / ✓ | ✓ / ✓ | ✓ / **unreliable** |
124 +| `thinking` | see §8 | enabled only | enabled only | ✓ |
125 +| `reasoning_effort` | ✓ | tolerated | tolerated | tolerated |
126 +| `tool_choice: required` | ✓ | 400 (thinking) | 400 (thinking) | 400 (thinking) |
127 +| `tool_choice: {function}` | 400 (thinking) | 400 | 400 | 400 (✓ when thinking disabled) |
128 +| `parallel_tool_calls` | ✓ | ✓ | ✓ | ✓ |
129 +| `developer` role | 400 | 400 | 400 | 400 |
130 +| unknown params | ignored | ignored | ignored | ignored |
131 +
132 +Exact rejection text: `{"error":{"message":"invalid temperature: only 1 is allowed for this model","type":"invalid_request_error"}}` (same pattern for `top_p: only 0.95`, `n: only 1`, `presence_penalty: only 0`, `frequency_penalty: only 0`, `logprobs: only false`). **Adapter rule: do not expose temperature/top_p/penalties/n for Kimi at all (or send only the fixed values); expose `stop`, `max_tokens`, `thinking` (K2.6), `reasoning_effort` (K3), JSON modes, tools.**
133 +
134 +## 10. Modalities, context, output limits
135 +
136 +- **Input**: text + image + video on **all 4 models** (`supports_image_in` / `supports_video_in: true` in `/models`; vision **probed OK on all 4** with a 32×32 PNG data URL). Output: text only. No audio, no image generation.
137 +- Image parts: `{type:"image_url", image_url:{url:"data:image/png;base64,…"}}` **and** the string form `{type:"image_url", image_url:"data:…"}` both accepted **(probed)**; `{type:"video_url", video_url:{url}}` for video (docs). **Public HTTP(S) URLs are rejected**: 400 `Invalid request: unsupported image url: https://…` **(probed)** — the adapter must download and inline as base64 (or upload via `/v1/files` purpose `image` and reference `ms://<file_id>`). Formats: jpeg/png/gif/webp/bmp/heic/heif (animated gif/webp billed as video), video mp4/mpeg/mov/avi/x-flv/mpg/webm/wmv/3gpp; recommended ≤ 4K images, ≤ 1080p video, request body ≤ 100 MB (docs). A 32×32 PNG cost **≈ 7 prompt tokens**; a 2×2 PNG was accepted but effectively ignored (0 extra tokens, K3 said "I don't see an image") **(probed)**. Image tokens are billed as normal input tokens (no separate image price on the pricing pages).
138 +- **Context windows** (`/models.context_length`, matches docs): kimi-k3 **1 048 576**; kimi-k2.6 / k2.7-code / k2.7-code-highspeed **262 144**. Overflow → 400 `Invalid request: Your request exceeded model token limit: 262144 (requested: 540015)` **(probed)**.
139 +- **Max output**: docs — K3 default `max_completion_tokens` 131 072, max 1 048 576; K2.x default 32 768. **The API does not validate the cap**: `max_tokens: 300000` on K2.x and `2000000` on K3 returned 200 **(probed)** — the adapter must clamp itself. Docs' 504 advice: use streaming for long generations.
140 +- Files: `/v1/files` purpose `file-extract``GET /v1/files/{id}/content` text that you put in a **system message** (no native file attachment in chat); "temporarily free" (docs).
141 +
142 +## 11. Web search & official tools **(probed)**
143 +
144 +Three different mechanisms exist; pricing $0.005 per successful search call + result tokens billed as prompt tokens (docs `/pricing/tools`).
145 +
146 +1. **Builtin `$web_search` on chat completions**`tools:[{type:"builtin_function", function:{name:"$web_search"}}]`. The search is executed **server-side during step 1**: the model returns `finish_reason: "tool_calls"` with `{"id":"t-web_search-6a9faef21a9e","type":"builtin_function","function":{"name":"$web_search","arguments":"{\"search_result\":{\"search_id\":\"7dd6…\"},\"usage\":{\"total_tokens\":7247}}"}}` (streamed in **2 chunks**: header with `arguments:""`, then the full arguments string) and `usage.completion_tokens: 1`. Step 2 = echo the assistant message verbatim + `{role:"tool", tool_call_id, name:"$web_search", content: <arguments verbatim>}`. **Works on kimi-k2.6 and kimi-k2.7-code** (K2.6: prompt_tokens 7 337, answer with Yahoo Finance URL inline; K2.7-code: 9 275) **(probed)**. **Broken on kimi-k3: step 2 → 400 `Invalid request: tokenization failed`** whatever we replayed (verbatim, without/with empty `reasoning_content`, with/without `reasoning_effort`); changing `type` to `"function"` gets a 200 but the search content is not injected (265 prompt tokens, model says the tool returned nothing). No citation structure: the URL appears only in the answer text.
147 +2. **Official tools ("formulas")** — the documented path for K3: `GET /v1/formulas/moonshot/web-search:latest/tools``[{type:"function", function:{name:"web_search", parameters:{query, classes?: [all|academic|social|library|finance|code|ecommerce|medical]}}}]`; pass them as normal tools; when the model calls `web_search` run `POST /v1/formulas/moonshot/web-search:latest/fibers` `{name:"web_search", arguments:"<json string>"}``{id:"fiber-…", object:"fiber", status:"succeeded", context:{input, encrypted_output:"----MOONSHOT ENCRYPTED BEGIN----…----MOONSHOT ENCRYPTED END----", references}}`; send `encrypted_output` as the tool message content → **200 on K3** (step 2 prompt 1 594 tokens, answer with source URL) **(probed)**. 12 public formulas listed by `GET /v1/formulas` (fetch, convert, web-search, code-runner, quickjs, excel, memory, date, base64, rethink, random-choice, mew); only web-search is billed. `context.references` may hold structured sources (not inspected).
148 +3. **Responses API `tools:[{type:"web_search"}]`** on kimi-k3 — fully server-side, one request: events `response.web_search_call.in_progress/searching/completed`, output items `web_search_call` (`action:{type:"search", query}`), `reasoning`, `message`; **`annotations: []`** (no url_citation), URL only inline; usage 7 888 input tokens for one search **(probed)**. Options `allowed_domains` (≤ 100), one web_search tool per request; `search_context_size`/`user_location` unsupported (docs).
149 +
150 +**Adapter recommendation:** web search = capability of **K2.6 / K2.7-code via `$web_search`** (simple 2-step, chat completions) and **K3 via formulas or Responses**; do not advertise `$web_search` on K3. Docs for K2.6 also say web search is incompatible with thinking on that model — probed OK with thinking on.
151 +
152 +## 12. Model listing & pricing **(docs + probed)**
153 +
154 +`GET /v1/models` **(probed)** fields: `id, object, created, owned_by:"moonshot", permission[], root, parent, context_length, supports_image_in, supports_video_in, supports_reasoning`, plus on K3 only: `supports_dynamic_tools: true`, `think_efforts` / `reasoning_efforts: {support:true, valid_efforts:["low","high","max"], default_effort:"max"}`, `supports_thinking_type: "only"`. No pricing, no max output in the API.
155 +
156 +| Model | ctx | in / cache-hit / out ($/M) | Batch (60 %) | Notes |
157 +|---|---|---|---|---|
158 +| kimi-k3 | 1 048 576 | **3.00 / 0.30 / 15.00** | not listed | flagship, 2.8 T params, `reasoning_effort`, dynamic tools, Responses + Messages APIs |
159 +| kimi-k2.7-code | 262 144 | **0.95 / 0.19 / 4.00** | 0.57 / 0.114 / 2.40 | coding, thinking always on |
160 +| kimi-k2.7-code-highspeed | 262 144 | **1.90 / 0.38 / 8.00** | — | same model, ~180–260 tok/s, exactly 2× price |
161 +| kimi-k2.6 | 262 144 | **0.95 / 0.16 / 4.00** | 0.57 / 0.10 / 2.40 | thinking switchable, cheapest cache hits |
162 +
163 +"Cache miss" price == normal input price; no cache write fee, no storage fee. Web search $0.005/call. Prices exclude taxes. File extraction free (temporary). Retired (404 `resource_not_found_error`) **(probed)**: `kimi-k2.5`, `moonshot-v1-*` (2026-08-31), `kimi-k2-*-preview` (2026-05-25), `kimi-k2-thinking`, `kimi-latest` (2026-01-28), `kimi-thinking-preview` (2025-11-11). No aliases exist.
164 +
165 +## 13. Prompt caching
166 +
167 +- **Automatic** on every model, prefix-based, no configuration; only requests whose prompt exceeds **256 tokens** are cached (docs). Reported as `usage.cached_tokens` + `usage.prompt_tokens_details.cached_tokens` and header `msh-context-cache-token-saved` **(probed: 821-token prompt → second call `cached_tokens: 512`, i.e. 256-token blocks; the ~17/92-token hidden prefix is cached on the very first call)**. Cache-hit price ≈ 10–17 % of input. `prompt_cache_key` (chat + Responses) improves hit rate for similar requests (docs). No TTL documented.
168 +- Provider-side state: none — Responses API is stateless here (`store: false`, `previous_response_id: null`, docs + probed echo). Nothing to opt out of for a BYOK app.
169 +
170 +## 14. Errors, rate limits, retries
171 +
172 +Error body: OpenAI-style `{"error":{"message":"…","type":"…"}}` (no `code` observed; docs mention an optional `code`).
173 +
174 +| Case **(probed)** | HTTP | body |
175 +|---|---|---|
176 +| Invalid key | **401** | `{"error":{"message":"Invalid Authentication","type":"invalid_authentication_error"}}` |
177 +| No Authorization header | 401 | `{"error":{"message":"Incorrect API key provided","type":"incorrect_api_key_error"}}` |
178 +| Unknown / retired model | **404** | `{"error":{"message":"Not found the model kimi-k99 or Permission denied","type":"resource_not_found_error"}}` |
179 +| Fixed param violated | 400 | `invalid temperature: only 1 is allowed for this model` (`invalid_request_error`) |
180 +| `developer` role | 400 | `Invalid request: role 'developer' is not allowed` |
181 +| Context overflow | 400 | `Invalid request: Your request exceeded model token limit: 262144 (requested: 540015)` |
182 +| Empty/malformed messages | 400 | `Invalid request: messages must not be empty` / `…field in the request (expected type []object) is illegal…` |
183 +| Public image URL | 400 | `Invalid request: unsupported image url: …` |
184 +| tool_choice vs thinking | 400 | `tool_choice 'required' is incompatible with thinking enabled` / `tool_choice 'specified' is incompatible with thinking enabled` |
185 +| Builtin replay on K3 | 400 | `Invalid request: tokenization failed` |
186 +| Concurrency/RPM | **429** | `{"error":{"message":"Organization Rate limit exceeded, please try again after 1 seconds","type":"rate_limit_reached_error"}}` and `…request reached organization max RPM: 100, please try again after 1 seconds`**no `retry-after` header** |
187 +| Engine busy | 429 | `{"error":{"message":"The engine is currently overloaded, please try again later","type":"engine_overloaded_error"}}` |
188 +| Content filter / quota | 400 / 429 | `content_filter` "The request was rejected because it was considered high risk"; `exceeded_current_quota_error` "Account balance is insufficient" (docs) |
189 +| Server | 500 / 503 / 504 | `server_error`, `server_unavailable`, gateway timeout → use streaming (docs) |
190 +
191 +Rate limits (docs `/pricing/limits`, per **user/organization**, computed with `max_completion_tokens`): Tier0 ($1 paid) 1 concurrent / 3 RPM / 500k TPM / 1.5M TPD; Tier1 ($10) 15 / 100 / 2M / ∞; Tier2 ($20) 40 / 100 / 3M; Tier3 ($100) 50 / 200 / 3M; Tier4 ($1 000) 60 / 200 / 4M; Tier5 ($3 000) 100 / 300 / 5M. Our key (`msh-gid: enterprise-tier-2`) hit the **100 RPM** ceiling during the parameter matrix **(probed)**. Interrupted 429 requests are not charged (docs). Risk-control throttling is permanent (docs).
192 +
193 +**Retry/timeout recommendation:** retry `rate_limit_reached_error` / `engine_overloaded_error` / 5xx with jittered backoff starting at ~1–3 s (the message says "after 1 seconds"; no header), cap attempts (engine_overloaded persisted 8× for highspeed+logprobs — surface it); never retry 400/401/404; disable SDK auto-retries (they eat RPM). Use streaming for anything long (504 risk) and a read timeout ≥ 5 min (K3 `max` effort). Map **401** (`invalid_authentication_error` / `incorrect_api_key_error`) to the app's invalid-credentials state; map **404 resource_not_found_error** to "model unavailable for this key/region" (the same error is used for a China-platform key on the international host).
194 +
195 +## 15. Lifecycle / naming
196 +
197 +- 4 active models, no aliases, no `-latest`. K2.6 = general (Apr 2026 era), K2.7-code / -highspeed = coding, K3 = flagship (1M ctx). Migration guidance: everything retired → kimi-k3.
198 +- Docs domain renamed to platform.kimi.ai (2026); API host unchanged. Keys are platform-specific (international vs .cn vs Kimi Code) — the same 401/404 errors are returned for a wrong-platform key (docs troubleshooting).
199 +- Partial mode (`{role:"assistant", content:"prefix", partial:true}` as last message) works **(probed on K2.6 non-thinking; output continues the prefix)**; with thinking models also replay `reasoning_content` and give a large `max_tokens` (docs).
200 +- K3 believed the current date was 2026-04-25 in a formula-tool answer (system-prompt date), while Responses web_search answered with the real date — do not rely on the model's date.
201 +
202 +## 16. Exact streaming code that worked
203 +
204 +```ts
205 +import OpenAI from "openai";
206 +const client = new OpenAI({ apiKey: process.env.KIMI_API_KEY, baseURL: "https://api.moonshot.ai/v1", timeout: 600_000, maxRetries: 0 });
207 +
208 +const stream = await client.chat.completions.create({
209 + model: "kimi-k2.6",
210 + messages: [{ role: "user", content: "Weather in Montreal? Use the tool." }],
211 + tools: [{ type: "function", function: { name: "get_weather", parameters: { type: "object", properties: { city: { type: "string" } }, required: ["city"] } } }],
212 + stream: true,
213 + stream_options: { include_usage: true },
214 + max_tokens: 16_000, // must cover reasoning + answer
215 + ...({ thinking: { type: "enabled" } } as any), // K2.6 only; K3 uses reasoning_effort: "low"|"high"|"max"
216 +});
217 +const acc = { reasoning: "", content: "", toolCalls: [] as any[] };
218 +for await (const chunk of stream) {
219 + const d = (chunk.choices[0]?.delta ?? {}) as any;
220 + if (d.reasoning_content) acc.reasoning += d.reasoning_content; // thinking pane
221 + if (d.content) acc.content += d.content;
222 + for (const tc of d.tool_calls ?? []) { // fragmented by index
223 + const t = (acc.toolCalls[tc.index] ??= { id: "", type: "function", function: { name: "", arguments: "" } });
224 + if (tc.id) t.id = tc.id; if (tc.function?.name) t.function.name += tc.function.name;
225 + if (tc.function?.arguments) t.function.arguments += tc.function.arguments;
226 + }
227 + if (chunk.choices[0]?.finish_reason) onFinish(chunk.choices[0].finish_reason); // stop | length | tool_calls
228 + if (chunk.usage) onUsage(chunk.usage); // final choices: [] chunk; reasoning_tokens inside completion_tokens
229 +}
230 +// replay: { role:"assistant", content: acc.content || null, reasoning_content: acc.reasoning, tool_calls: acc.toolCalls }
231 +```
232 +
233 +Raw fetch SSE (`research/kimi/lib.ts``rawSSE`): split on `\n\n`, take `data:` lines, `JSON.parse`, stop at `[DONE]`. For `/v1/responses` the stream has `event: <name>` + `data:` pairs; the JSON `type` field equals the event name.
234 +
235 +## 17. Probe results table
236 +
237 +| # | Probe | Model(s) | Result |
238 +|---|---|---|---|
239 +| 00 | `GET /models`, `/models/{id}`, `/users/me/balance`, `POST /tokenizers/estimate-token-count` | — | all 200; 4 models with `context_length`, image/video/reasoning flags, K3 `think_efforts`; balance 54.79 USD; estimate 93 tokens |
240 +| 01a | tiny chat completion `max_tokens: 200` | 4 models | all 200; `reasoning_content` on all 4; `completion_tokens` includes `reasoning_tokens`; K3 7.1 s, highspeed 1.0 s |
241 +| 01b | streaming with/without `include_usage` | 4 models | `data:` only; `role``reasoning_content``content`; usage in finish chunk's choice always + `choices: []` chunk with include_usage; `cached_tokens` top-level and in `prompt_tokens_details`; K2.6/K3 hit `length` at 200 tokens inside reasoning |
242 +| 02 | param matrix (38 variants) | 4 models | §9: temperature/top_p/n/penalties fixed (400 otherwise); `stop` ≤ 5; `seed` ok; `developer` 400; `logprobs` K3 400 / highspeed 429; `thinking.disabled` ok on K2.6 **and K3**, 400 on K2.7; `reasoning_effort` `medium`/`none` accepted on K3/K2.6, `none` 400 on K2.7; `tool_choice required` 400 on K2.x, function 400 on all (thinking); json modes ok; unknown params ignored; hit 100 RPM (429 bodies captured) |
243 +| 03 | streamed function call round trip; replay with / without / empty `reasoning_content` | 4 models (+K2.6 thinking off) | fragmented `tool_calls` deltas, ids `get_weather_0`, `finish_reason: tool_calls`; all three replay variants 200 on all models; K2.6 non-thinking returns `reasoning_content: ""` |
244 +| 04 | `json_object`, `json_schema strict`, vision 32×32 / 2×2 PNG (object + string `image_url`), public URL | 4 models | json_object valid on 4/4; json_schema valid on K3/K2.7/K2.7-hs, **prose on K2.6**; vision 200 on 4/4, ≈7 tokens per 32×32, 2×2 ignored; public URL 400 |
245 +| 05 | invalid key / no auth / unknown + 5 retired models / malformed / missing messages / oversize `max_tokens` / 540k-token prompt / forced tool w/o thinking / cache / partial | mixed | 401 ×2, 404 ×6, 400 ×2, oversize max_tokens accepted (K2.x 300k, K3 2M), overflow 400 with exact limit, forced tool ok on K2.6 non-thinking, `cached_tokens: 512` on 2nd call, partial mode continues prefix |
246 +| 06/06b | `$web_search` builtin, streamed then variants | kimi-k3 | step 1 ok (server-side search, `search_id`, 7.2k search tokens); **step 2 400 `tokenization failed`** for verbatim / no reasoning / empty reasoning / no `name`; `type:"function"` → 200 but no search content |
247 +| 07 | Responses API `web_search` (stream) + plain; Responses on K2.6; `$web_search` on K2.6 + K2.7-code; `GET /formulas*` | k3, k2.6, k2.7-code | Responses: 16 event types, `web_search_call` item, `annotations: []`, 7 888 input tokens; K2.6 Responses 200; `$web_search` round trip **200 on K2.6** (7 337 prompt tokens) and K2.7-code step 1 ok; formulas list 12 tools, web-search declaration fetched |
248 +| 08 | K3 `$web_search` default effort; K2.7-code `$web_search` step 2; formulas fiber round trip on K3; K2.6 json_schema thinking on/off; highspeed logprobs; OpenAI SDK smoke test | mixed | K3 builtin still 400; K2.7-code step 2 200 (9 275 prompt tokens); fiber `succeeded` with `encrypted_output` → K3 answer with URL (200); K2.6 schema prose (thinking) / JSON after blank lines (no thinking); highspeed logprobs 429 ×8; SDK streams `reasoning_content` + tool call + usage |
249 +
250 +## Documentation pages used (all fetched 2026-09-08; every `platform.moonshot.ai` URL redirected 301 to `platform.kimi.ai`)
251 +
252 +- https://platform.kimi.ai/docs/introduction · https://platform.kimi.ai/docs/llms.txt (index) · https://platform.kimi.ai/docs/models · https://platform.kimi.ai/docs/platform-changelog · https://platform.kimi.ai/docs/openapi.json
253 +- https://platform.kimi.ai/docs/api/overview · https://platform.kimi.ai/docs/api/chat · https://platform.kimi.ai/docs/api/models-overview (Model Parameter Reference) · https://platform.kimi.ai/docs/api/responses · https://platform.kimi.ai/docs/api/messages · https://platform.kimi.ai/docs/api/list-models · https://platform.kimi.ai/docs/api/estimate · https://platform.kimi.ai/docs/api/balance · https://platform.kimi.ai/docs/api/files-upload · https://platform.kimi.ai/docs/api/errors
254 +- https://platform.kimi.ai/docs/guide/ · …/guide/kimi-k3-quickstart · …/guide/kimi-k2-6-quickstart · …/guide/kimi-k2-7-code-quickstart · …/guide/use-thinking-models · …/guide/use-reasoning-effort · …/guide/engage-in-multi-turn-conversations-using-kimi-api · …/guide/utilize-the-streaming-output-feature-of-kimi-api · …/guide/use-json-mode-feature-of-kimi-api · …/guide/response_format · …/guide/use-partial-mode-feature-of-kimi-api · …/guide/use-kimi-vision-model · …/guide/use-context-caching-feature-of-kimi-api · …/guide/use-kimi-api-to-complete-tool-calls · …/guide/use-tool-choice · …/guide/use-dynamic-tool-loading · …/guide/kimi-k3-tool-calling-best-practice · …/guide/use-web-search · …/guide/use-official-tools · …/guide/troubleshooting · …/guide/auto-reconnect
255 +- https://platform.kimi.ai/docs/pricing/chat · …/pricing/chat-k3 · …/pricing/chat-k27-code · …/pricing/chat-k26 · …/pricing/tools · …/pricing/batch · …/pricing/limits
256 +- Redirected (301 → platform.kimi.ai): all `/docs/*` paths of platform.moonshot.ai given in the brief. `https://platform.kimi.ai/sitemap.xml` only lists 2 pages; use `llms.txt` instead. Not probed: `/anthropic/v1/messages`, files upload, batch.
added docs/provider-research/kimi.models.json +202 −0
@@ -0,0 +1,202 @@
1 +[
2 + {
3 + "id": "kimi-k3",
4 + "displayName": "Kimi K3",
5 + "family": "kimi-k3",
6 + "contextTokens": 1048576,
7 + "maxOutputTokens": 1048576,
8 + "capabilities": {
9 + "text": true,
10 + "vision": true,
11 + "audioInput": false,
12 + "audioOutput": false,
13 + "imageGeneration": false,
14 + "video": true,
15 + "reasoning": true,
16 + "tools": true,
17 + "structuredOutput": true,
18 + "streaming": true,
19 + "files": "file-extract-to-system-message",
20 + "webSearch": "formulas-or-responses-only"
21 + },
22 + "parameters": {
23 + "temperature": false,
24 + "topP": false,
25 + "topK": false,
26 + "maxTokens": true,
27 + "reasoningEffort": ["low", "high", "max"],
28 + "thinkingBudget": false,
29 + "stop": true,
30 + "seed": "accepted-undocumented",
31 + "frequencyPenalty": false,
32 + "presencePenalty": false
33 + },
34 + "pricing": {
35 + "inputPerMillion": 3.0,
36 + "cachedInputPerMillion": 0.3,
37 + "outputPerMillion": 15.0,
38 + "longContextThresholdTokens": null,
39 + "longContextInputPerMillion": null,
40 + "longContextCachedInputPerMillion": null,
41 + "longContextOutputPerMillion": null,
42 + "imageInputPerMillionTokens": 3.0,
43 + "webSearchPerCall": 0.005,
44 + "batchInputPerMillion": null,
45 + "batchCachedInputPerMillion": null,
46 + "batchOutputPerMillion": null
47 + },
48 + "status": "active",
49 + "aliases": [],
50 + "notes": "Flagship (2.8T params, 1M context). Thinking always on per docs (/models: supports_thinking_type 'only'); reasoning_effort low|high|max, default max ('medium' also accepted). UNDOCUMENTED (probed): thinking:{type:'disabled'} and reasoning_effort:'none' return 200 with no reasoning. temperature fixed 1, top_p fixed 0.95, n=1, penalties 0 (any other value -> 400 'invalid temperature: only 1 is allowed for this model'). logprobs -> 400 'only false is allowed'. tool_choice 'required' OK, forcing a specific function -> 400 (thinking). max_completion_tokens default 131072, documented max 1048576 (API accepts 2000000 without error -> clamp client-side). Hidden system prompt ~95 tokens. Builtin $web_search is BROKEN on this model (step 2 -> 400 'tokenization failed'): use official tools (GET /v1/formulas/moonshot/web-search:latest/tools + POST .../fibers, encrypted_output as tool content) or /v1/responses tools:[{type:'web_search'}] (no annotations, URL inline; ~7.9k input tokens per search). Dynamic tool loading (system message with tools) K3-only. Also available on /v1/responses and /anthropic/v1/messages. Batch price not published for K3."
51 + },
52 + {
53 + "id": "kimi-k2.7-code",
54 + "displayName": "Kimi K2.7 Code",
55 + "family": "kimi-k2.7-code",
56 + "contextTokens": 262144,
57 + "maxOutputTokens": 32768,
58 + "capabilities": {
59 + "text": true,
60 + "vision": true,
61 + "audioInput": false,
62 + "audioOutput": false,
63 + "imageGeneration": false,
64 + "video": true,
65 + "reasoning": true,
66 + "tools": true,
67 + "structuredOutput": true,
68 + "streaming": true,
69 + "files": "file-extract-to-system-message",
70 + "webSearch": true
71 + },
72 + "parameters": {
73 + "temperature": false,
74 + "topP": false,
75 + "topK": false,
76 + "maxTokens": true,
77 + "reasoningEffort": false,
78 + "thinkingBudget": false,
79 + "stop": true,
80 + "seed": "accepted-undocumented",
81 + "frequencyPenalty": false,
82 + "presencePenalty": false
83 + },
84 + "pricing": {
85 + "inputPerMillion": 0.95,
86 + "cachedInputPerMillion": 0.19,
87 + "outputPerMillion": 4.0,
88 + "longContextThresholdTokens": null,
89 + "longContextInputPerMillion": null,
90 + "longContextCachedInputPerMillion": null,
91 + "longContextOutputPerMillion": null,
92 + "imageInputPerMillionTokens": 0.95,
93 + "webSearchPerCall": 0.005,
94 + "batchInputPerMillion": 0.57,
95 + "batchCachedInputPerMillion": 0.114,
96 + "batchOutputPerMillion": 2.4
97 + },
98 + "status": "active",
99 + "aliases": [],
100 + "notes": "Coding model, 256k context. Thinking ALWAYS on: thinking:{type:'disabled'} and reasoning_effort:'none' -> 400 'invalid thinking: only type=enabled is allowed for this model'; only {type:'enabled', keep:'all'} meaningful (Preserved Thinking always on -> replay reasoning_content on assistant messages; omission is tolerated by the API, probed). reasoning_effort low|medium|high|max silently accepted (no documented effect). Fixed temperature 1 / top_p 0.95 / n 1 / penalties 0. logprobs accepted. tool_choice 'required' and forced function -> 400 with thinking. json_object + strict json_schema reliable (docs: most stable model for structured output). Default max output 32768 (docs); API does not validate larger values. $web_search builtin round trip works (probed, ~9.3k prompt tokens for one search)."
101 + },
102 + {
103 + "id": "kimi-k2.7-code-highspeed",
104 + "displayName": "Kimi K2.7 Code (High-Speed)",
105 + "family": "kimi-k2.7-code",
106 + "contextTokens": 262144,
107 + "maxOutputTokens": 32768,
108 + "capabilities": {
109 + "text": true,
110 + "vision": true,
111 + "audioInput": false,
112 + "audioOutput": false,
113 + "imageGeneration": false,
114 + "video": true,
115 + "reasoning": true,
116 + "tools": true,
117 + "structuredOutput": true,
118 + "streaming": true,
119 + "files": "file-extract-to-system-message",
120 + "webSearch": null
121 + },
122 + "parameters": {
123 + "temperature": false,
124 + "topP": false,
125 + "topK": false,
126 + "maxTokens": true,
127 + "reasoningEffort": false,
128 + "thinkingBudget": false,
129 + "stop": true,
130 + "seed": "accepted-undocumented",
131 + "frequencyPenalty": false,
132 + "presencePenalty": false
133 + },
134 + "pricing": {
135 + "inputPerMillion": 1.9,
136 + "cachedInputPerMillion": 0.38,
137 + "outputPerMillion": 8.0,
138 + "longContextThresholdTokens": null,
139 + "longContextInputPerMillion": null,
140 + "longContextCachedInputPerMillion": null,
141 + "longContextOutputPerMillion": null,
142 + "imageInputPerMillionTokens": 1.9,
143 + "webSearchPerCall": 0.005,
144 + "batchInputPerMillion": null,
145 + "batchCachedInputPerMillion": null,
146 + "batchOutputPerMillion": null
147 + },
148 + "status": "active",
149 + "aliases": [],
150 + "notes": "Same model/behaviour as kimi-k2.7-code served at ~180 tok/s (up to 260 tok/s short context), exactly 2x the price. Thinking always on (disabled -> 400), fixed sampling params, tool_choice required/function -> 400. logprobs: every attempt returned 429 engine_overloaded_error (8/8) -> treat as unsupported, never send. Fastest probe latency (~1 s for a trivial prompt). $web_search not probed on this variant (works on kimi-k2.7-code). Batch pricing not listed."
151 + },
152 + {
153 + "id": "kimi-k2.6",
154 + "displayName": "Kimi K2.6",
155 + "family": "kimi-k2.6",
156 + "contextTokens": 262144,
157 + "maxOutputTokens": 32768,
158 + "capabilities": {
159 + "text": true,
160 + "vision": true,
161 + "audioInput": false,
162 + "audioOutput": false,
163 + "imageGeneration": false,
164 + "video": true,
165 + "reasoning": "switchable",
166 + "tools": true,
167 + "structuredOutput": "unreliable-json-schema-with-thinking",
168 + "streaming": true,
169 + "files": "file-extract-to-system-message",
170 + "webSearch": true
171 + },
172 + "parameters": {
173 + "temperature": false,
174 + "topP": false,
175 + "topK": false,
176 + "maxTokens": true,
177 + "reasoningEffort": false,
178 + "thinkingBudget": false,
179 + "stop": true,
180 + "seed": "accepted-undocumented",
181 + "frequencyPenalty": false,
182 + "presencePenalty": false
183 + },
184 + "pricing": {
185 + "inputPerMillion": 0.95,
186 + "cachedInputPerMillion": 0.16,
187 + "outputPerMillion": 4.0,
188 + "longContextThresholdTokens": null,
189 + "longContextInputPerMillion": null,
190 + "longContextCachedInputPerMillion": null,
191 + "longContextOutputPerMillion": null,
192 + "imageInputPerMillionTokens": 0.95,
193 + "webSearchPerCall": 0.005,
194 + "batchInputPerMillion": 0.57,
195 + "batchCachedInputPerMillion": 0.1,
196 + "batchOutputPerMillion": 2.4
197 + },
198 + "status": "active",
199 + "aliases": [],
200 + "notes": "General model, 256k context, text+image+video. The only model with a documented thinking switch: thinking:{type:'enabled'} (default, temp fixed 1.0) | {type:'disabled'} (temp fixed 0.6) | {type:'enabled', keep:'all'} (Preserved Thinking). reasoning_effort is undocumented here but accepted: low|medium|high|max no visible effect, 'none' disables thinking (probed). Fixed sampling params (400 otherwise). With thinking: tool_choice 'required' and forced function -> 400; with thinking disabled forced function works. json_object reliable; strict json_schema returned prose twice with thinking on and JSON preceded by ~300 blank lines with thinking off -> validate client-side (docs: 'occasionally unstable with complex schemas'). $web_search builtin round trip works (probed, ~7.3k prompt tokens per search) even with thinking on (docs claim incompatibility). Partial mode works (probed, non-thinking). Also answers on /v1/responses although docs list K3 only. Cheapest cache-hit price. Hidden prompt ~17 tokens."
201 + }
202 +]
203