Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,303 changes: 2,303 additions & 0 deletions provider-kimi/Cargo.lock

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions provider-kimi/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[workspace]

[package]
name = "provider-kimi"
version = "1.0.0"
edition = "2021"
publish = false
license = "Apache-2.0"
description = "Moonshot (Kimi) Chat Completions provider worker behind llm-router."

[[bin]]
name = "provider-kimi"
path = "src/main.rs"

[lib]
path = "src/lib.rs"

[dependencies]
llm-router = { path = "../llm-router" }
# Must match llm-router's pin so the provider and router share one IIIClient type.
iii-sdk = "=0.21.6"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
# Must stay on the same schemars major as iii-sdk so the derived
# request/response schemas match what the SDK emits at registration.
schemars = "0.8"
tokio = { version = "1", features = ["macros", "rt-multi-thread", "sync", "time", "signal"] }
futures = "0.3"
reqwest = { version = "0.12", default-features = false, features = ["json", "stream", "rustls-tls"] }
clap = { version = "4", features = ["derive", "env"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] }

[dev-dependencies]
uuid = { version = "1", features = ["v4"] }
77 changes: 77 additions & 0 deletions provider-kimi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# provider-kimi

Moonshot (Kimi) Chat Completions provider worker behind
[llm-router](../llm-router/). Moonshot's API is OpenAI Chat
Completions–compatible, so this worker forks the shared provider scaffolding
and adds Kimi's `reasoning_content` thinking stream.

Implements the provider protocol from
`tech-specs/2026-06-agentic/llm-router.md`: `provider::kimi::stream`
(SSE chunks → `AssistantMessageEvent` frames into a router-owned channel) and
`provider::kimi::refresh_models` (live `GET /v1/models` filtered to Kimi /
Moonshot chat families, enriched with a curated capability snapshot →
`router::models::reconcile`).

## Behavior

- **Registration:** self-declares via `router::provider::register` with
backoff until acked, and re-declares on the `router::ready` trigger type.
The declaration carries no static `models` slice — `GET /v1/models` is the
source of truth and a refresh fires right after registration. Defaults:
`api_url: https://api.moonshot.ai/v1/chat/completions`,
`credential_env_var: MOONSHOT_API_KEY`.
- **Identity binding:** the router returns a `registration_token` on first
registration; it is persisted in iii-state (scope `provider-kimi`,
key `registration_token`) and presented on every later
`register`/`resolve`/`reconcile`. If that state is lost the router rejects
re-registration — the operator must clear the binding on the router side.
- **Credentials:** resolved per request via `router::provider::resolve`
(config slice → `MOONSHOT_API_KEY` env on the router → none). Both
`api_key` and `oauth` credential shapes are sent as `Authorization:
Bearer`; v1 performs no OAuth refresh.
- **Liveness:** `ping` at least every 30s of upstream silence; a failed
channel write (caller gone / `router::abort`) drops the SSE receiver and
aborts the in-flight HTTP request.
- **Errors:** 401/403 → `auth_expired`, 429 → `rate_limited` (except a quota
wall, `exceeded_current_quota_error` → `permanent`),
context-length errors → `context_overflow`, 5xx/`engine_overloaded_error` /
network → `transient`, other 4xx → `permanent`. No transport retries here —
the router owns retry policy.
- **Request shape vs OpenAI:** Moonshot accepts the classic `max_tokens` param
(not `max_completion_tokens`) and has no `reasoning_effort` knob.
- **Thinking:** Kimi thinking models (Kimi K2 Thinking, `kimi-thinking-preview`)
stream their reasoning in `delta.reasoning_content` before the answer text.
This worker surfaces it as a `Thinking` content block via
`ThinkingStart`/`ThinkingDelta`/`ThinkingEnd` (no replay signature). The
model itself decides whether it thinks; `thinking_level` is advisory.
- **Structured output:** Moonshot supports JSON mode only
(`response_format: {"type":"json_object"}`), not OpenAI's strict
`json_schema` mode. A requested schema is mapped to `json_object` and a
report-and-continue warning rides the final message (the caller must mention
"JSON" in the prompt). Curated records declare
`supports_structured_output: true`.
- **Prompt caching:** Moonshot context caching is automatic — no request
markers. `prompt_tokens_details.cached_tokens` lands on `usage.cache_read`.
- **Curated snapshot:** `src/curated.rs` carries display names / context
windows / output ceilings / capability flags / pricing for known Kimi and
Moonshot families; conservative defaults for unknown ones. Context windows
and pricing are best-effort placeholders — **verify against
platform.moonshot.ai before release**. Discovery supplies only bare ids.

## Tests

```bash
cargo test # unit (pure modules + TCP stubs)
III_ENGINE_BIN=$(which iii) cargo test --test integration -- --test-threads=1
```

The integration suite spawns a real engine, the real router (path dep), this
provider, and a local stub upstream — no external API calls anywhere.

## Running

The binary takes the standard worker CLI flags: `--url` (engine WebSocket,
default `ws://127.0.0.1:49134`, falls back to the `III_WS_URL` environment
variable), `--manifest` (print the registry manifest and exit), and
`--config` (accepted but ignored with a warning — provider config comes
from the `llm-router` configuration entry).
6 changes: 6 additions & 0 deletions provider-kimi/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
fn main() {
println!(
"cargo:rustc-env=TARGET={}",
std::env::var("TARGET").expect("TARGET must be set by Cargo build scripts")
);
}
8 changes: 8 additions & 0 deletions provider-kimi/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# provider-kimi has no file-based configuration.
#
# Credentials, `api_url`, and `max_tokens` arrive per request from
# llm-router's resolve step; the provider block lives in the engine's
# `llm-router` configuration entry (README § Configuration).
#
# This file exists to satisfy the standard worker layout
# (docs/sops/new-worker.md §2). Keys placed here are ignored with a warning.
10 changes: 10 additions & 0 deletions provider-kimi/iii-permissions.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Agent permissions for the provider-kimi worker.
# Spec: tech-specs/2026-06-agentic/llm-router.md § Security.
version: 1

rules:
# Direct provider calls bypass the router's accounting, budgets, and retry
# policy — never agent-callable. The router invokes these worker-to-worker.
- '!provider::kimi::stream'
- '!provider::kimi::refresh_models'
- '!provider::kimi::on_router_ready'
11 changes: 11 additions & 0 deletions provider-kimi/iii.worker.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
iii: v1
name: provider-kimi
language: rust
deploy: binary
manifest: Cargo.toml
bin: provider-kimi
description: Moonshot (Kimi) Chat Completions provider worker; implements provider::kimi::stream and provider::kimi::refresh_models behind llm-router.

dependencies:
iii-state: "^0.21.6"
llm-router: "^1.0.0"
Loading
Loading