diff --git a/README.md b/README.md index 7bb72f616d4..ef5bd845ab5 100644 --- a/README.md +++ b/README.md @@ -1101,14 +1101,19 @@ OmniRoute stands on the shoulders of giants. It started as a fork of **[9router]
- Kimi (Moonshot AI) + + + + Kimi (Moonshot AI) + +
Kimi
Moonshot AI

Official Supporter
Thanks to Kimi (Moonshot AI) for sponsoring this project! Kimi is the AI lab behind the open-weight K2 and K3 model families — Kimi K3 delivers a 1M-token context window, native vision and frontier-level coding at a fraction of closed-model prices, and works out of the box with Claude Code, Codex and every coding tool OmniRoute serves.

- Where the sponsorship goes: Kimi's API credits power OmniRoute's AI-validated release pipeline — the merge validation powered by Kimi K3 stage that reviews every pull request before it ships — plus day-to-day feature development. First-class Kimi support ships on both rails: the direct Moonshot API (kimi-k3) and the Kimi Code coding plan (OAuth and API key). OmniRoute is also the first Brazilian open-source project in Kimi's support program. Get a Moonshot API key → + Where the sponsorship goes: Kimi's API credits power OmniRoute's AI-validated release pipeline — the merge validation powered by Kimi K3 stage that reviews every pull request before it ships — plus day-to-day feature development. First-class Kimi support ships on both rails: the direct Kimi API (kimi-k3) and the Kimi Code coding plan (OAuth and API key). OmniRoute is also the first Brazilian open-source project in Kimi's support program. Get a Kimi API key →
diff --git a/docs/guides/USER_GUIDE.md b/docs/guides/USER_GUIDE.md index 4dbca34ece5..431a31c7ac4 100644 --- a/docs/guides/USER_GUIDE.md +++ b/docs/guides/USER_GUIDE.md @@ -192,7 +192,7 @@ Models: #### Kimi K2 ($9/month flat) -1. Subscribe: [Moonshot AI](https://platform.moonshot.ai) +1. Subscribe: [Moonshot AI](https://platform.kimi.ai?aff=omniroute) 2. Get API key → Dashboard → Add API Key **Use:** `kimi/kimi-k2.5` — **Pro Tip:** Fixed $9/month for 10M tokens = $0.90/1M effective cost! diff --git a/docs/reference/PROVIDER_REFERENCE.md b/docs/reference/PROVIDER_REFERENCE.md index 636f8a27355..43a13f2f634 100644 --- a/docs/reference/PROVIDER_REFERENCE.md +++ b/docs/reference/PROVIDER_REFERENCE.md @@ -52,7 +52,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each | `gitlab-duo` | `gitlab-duo` | GitLab Duo | OAuth | [link](https://docs.gitlab.com/user/duo_agent_platform/code_suggestions/) | OAuth application with ai_features + read_user scopes. Configure GITLAB_DUO_OAUTH_CLIENT_ID and optionally GITLAB_DUO_OAUTH_CLIENT_SECRET on this OmniRoute instance. | | `grok-cli` | `gc` | Grok Build | OAuth | — | Paste your ~/.grok/auth.json (or the JWT access token) from the Grok Build CLI; refresh_token is rotated automatically. | | `kilocode` | `kc` | Kilo Code | OAuth | — | — | -| `kimi-coding` | `kmc` | Kimi Code CLI | OAuth | — | Sign in with the same Kimi account used by Kimi Code CLI. OmniRoute uses the CLI OAuth flow and Kimi Coding Plan endpoints. | +| `kimi-coding` | `kmc` | Kimi Code CLI | OAuth | [link](https://www.kimi.com/code?aff=omniroute) | Sign in with the same Kimi account used by Kimi Code CLI. OmniRoute uses the CLI OAuth flow and Kimi Coding Plan endpoints. | | `kiro` | `kr` | Kiro AI | OAuth | — | Free tier: 50 credits/month (~25K–100K tokens). ⚠️ Kiro ToS prohibits third-party proxy/harness use. | | `qoder` | `if` | Qoder | OAuth | — | — | | `trae` | `tr` | Trae | OAuth | [link](https://trae.ai) | Trae is an AI-native IDE by ByteDance (SOLO remote agent). Authorize via trae.ai in the popup, or sign in at solo.trae.ai and paste the Cloud-IDE-JWT (sent as 'Authorization: Cloud-IDE-JWT ', ~14-day lifetime) as the access token; web_id/biz_user_id/user_unique_id/scope/tenant/region propagate via providerSpecificData. No headless refresh for pasted tokens — re-paste on expiry. | @@ -78,7 +78,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each | `grok-web` | `gw` | Grok Web (Subscription) | Web cookie | [link](https://grok.com) | Paste the full grok.com cookie line from DevTools → Application → Cookies. Include both `sso` and `sso-rw` (e.g. `sso=...; sso-rw=...`) — Grok's anti-bot rejects `sso` on its own. | — | | `huggingchat` | `huggingchat` | HuggingChat (Free) | Web cookie | [link](https://huggingface.co/chat) | Paste the full Cookie header from huggingface.co/chat (DevTools → Network → /chat/conversation → Request Headers → Cookie). It should include hf-chat and may also include token / aws-waf-token. | — | | `inner-ai` | `in-ai` | Inner.ai (Subscription) | Web cookie | [link](https://app.innerai.com) | Paste your token cookie and email separated by a space: open DevTools → Application → Cookies → .innerai.com, copy the token value, then append a space and your Inner.ai login email. Example: eyJhbG... user@example.com | emulated | -| `kimi-web` | `kimi-web` | Kimi Web | Web cookie | [link](https://www.kimi.com) | Paste access_token from www.kimi.com DevTools → Application → Local Storage. A legacy kimi-auth cookie is also accepted. | — | +| `kimi-web` | `kimi-web` | Kimi Web | Web cookie | [link](https://www.kimi.com/code?aff=omniroute) | Paste access_token from www.kimi.com DevTools → Application → Local Storage. A legacy kimi-auth cookie is also accepted. | — | | `lmarena` | `lma` | Arena (Free) | Web cookie | [link](https://arena.ai) | Paste the full Cookie header from arena.ai (DevTools → Network → request → Cookie). Include arena-auth-prod-v1.0/.1… and cf_clearance/__cf_bm when present. OmniRoute uses Chrome TLS impersonation; if Arena still 403s, set providerSpecificData.recaptchaV3Token from a live browser session. | — | | `microsoft-designer-web` | `msdesigner` | Microsoft Designer (Image Generation) | Web cookie | [link](https://designer.microsoft.com) | Sign in at designer.microsoft.com, then open DevTools → Network, generate an image, and find the request to DallE.ashx?action=GetDallEImagesCogSci. Copy the value of its Authorization: Bearer header (the access_token — no 'Bearer ' prefix). The token is short-lived; this is an unofficial, reverse-engineered integration. | — | | `muse-spark-web` | `ms-web` | Muse Spark Web (Meta AI) | Web cookie | [link](https://www.meta.ai) | Paste your ecto_1_sess value or full cookie header from meta.ai | emulated | @@ -185,8 +185,8 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each | `kenari` | `kenari` | Kenari | API key | [link](https://kenari.id) | Use your Kenari API key (kn-...) in Authorization: Bearer . Fully OpenAI-compatible. API base URL: https://kenari.id/v1. | | `kie` | `kie` | KIE.AI | API key | [link](https://kie.ai) | — | | `kilo-gateway` | `kg` | Kilo Gateway | API key, aggregator | [link](https://kilo.ai) | — | -| `kimi` | `kimi` | Kimi (Legacy Moonshot API) | API key | [link](https://platform.moonshot.ai) | — | -| `kimi-coding-apikey` | `kmca` | Kimi Code API Key | API key | [link](https://www.kimi.com/code) | — | +| `kimi` | `kimi` | Kimi (Legacy Moonshot API) | API key | [link](https://platform.kimi.ai?aff=omniroute) | — | +| `kimi-coding-apikey` | `kmca` | Kimi Code API Key | API key | [link](https://www.kimi.com/code?aff=omniroute) | — | | `lambda-ai` | `lambda` | Lambda AI | API key | [link](https://lambda.ai) | — | | `laozhang` | `lz` | LaoZhang AI | API key, aggregator | [link](https://api.laozhang.ai) | — | | `leonardo` | `leo` | Leonardo AI | API key, video | [link](https://leonardo.ai) | Get API key at leonardo.ai/developer | @@ -203,7 +203,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each | `modal` | `mdl` | Modal | API key, enterprise | [link](https://modal.com/docs) | Use the bearer token that protects your Modal deployment, if enabled. Base URL should point to your OpenAI-compatible Modal app, for example https://--.modal.run/v1. | | `modelscope` | `ms` | ModelScope | API key | [link](https://modelscope.cn) | Free tier via ModelScope API-Inference — Alibaba account required. | | `monsterapi` | `monster` | MonsterAPI | API key | [link](https://monsterapi.ai) | Get API key at monsterapi.ai | -| `moonshot` | `moonshot` | Moonshot AI | API key | [link](https://platform.moonshot.ai) | — | +| `moonshot` | `moonshot` | Kimi | API key | [link](https://platform.kimi.ai?aff=omniroute) | — | | `morph` | `morph` | Morph | API key | [link](https://morphllm.com) | Free tier: 250K credits/month, $0 | | `nanogpt` | `nanogpt` | NanoGPT | API key | [link](https://nano-gpt.com) | — | | `navy` | `navy` | NavyAI | API key | [link](https://api.navy) | Create a free API key from the NavyAI dashboard, then paste it here as a Bearer token. | diff --git a/public/providers/kimi-logomark-dark.svg b/public/providers/kimi-logomark-dark.svg new file mode 100644 index 00000000000..29bceeade81 --- /dev/null +++ b/public/providers/kimi-logomark-dark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/providers/kimi-logomark-light.svg b/public/providers/kimi-logomark-light.svg new file mode 100644 index 00000000000..5c8451f83c9 --- /dev/null +++ b/public/providers/kimi-logomark-light.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/app/(dashboard)/dashboard/KimiSponsorBanner.tsx b/src/app/(dashboard)/dashboard/KimiSponsorBanner.tsx new file mode 100644 index 00000000000..8e56772742f --- /dev/null +++ b/src/app/(dashboard)/dashboard/KimiSponsorBanner.tsx @@ -0,0 +1,96 @@ +"use client"; + +import { useState } from "react"; +import { useTranslations } from "next-intl"; +import ProviderIcon from "@/shared/components/ProviderIcon"; +import { APP_CONFIG } from "@/shared/constants/appConfig"; +import { shouldShowKimiSponsorBanner } from "./kimiSponsorBanner"; + +// Official Kimi partnership tracking link — keep in sync with README.md's +// Sponsors section and the aff links wired in the providers onboarding UI +// (ProviderPageHeader.tsx). +const KIMI_CODING_AFF_URL = "https://www.kimi.com/code?aff=omniroute"; + +// Versioned dismissal key — bump the suffix (e.g. `-v2`) if the banner's +// offer/copy ever changes materially enough to warrant re-showing it to +// users who already dismissed the previous version. +const DISMISS_STORAGE_KEY = "omniroute-kimi-sponsor-banner-dismissed-v1"; + +function isNotDismissed(): boolean { + try { + return !localStorage.getItem(DISMISS_STORAGE_KEY); + } catch { + return true; + } +} + +/** + * Dismissable banner announcing the Kimi (Moonshot AI) official OmniRoute + * partnership on the dashboard home page. Self-contained: reads the app's own + * version (APP_CONFIG.version) to decide whether it is still inside the + * agreed display window (see kimiSponsorBanner.ts) and persists dismissal via + * localStorage, mirroring RiskNoticeBanner's lazy-useState pattern. The + * logomark reuses so it stays + * theme-aware for free via the THEMED_SVGS wiring in ProviderIcon.tsx. + */ +export default function KimiSponsorBanner() { + const t = useTranslations("kimiSponsorBanner"); + const [visible, setVisible] = useState(isNotDismissed); + + if (!visible || !shouldShowKimiSponsorBanner(APP_CONFIG.version)) { + return null; + } + + const dismiss = () => { + try { + localStorage.setItem(DISMISS_STORAGE_KEY, "true"); + } catch { + // ignore — worst case the banner reappears next visit + } + setVisible(false); + }; + + return ( +
+
+
+ +
+
+

{t("title")}

+

{t("description")}

+
+
+ +
+
+ + {t("cta")} + + + {t("partnerLinkNote")} +
+ +
+
+ ); +} diff --git a/src/app/(dashboard)/dashboard/combos/KimiComboPresetCard.tsx b/src/app/(dashboard)/dashboard/combos/KimiComboPresetCard.tsx new file mode 100644 index 00000000000..b014ab5d08f --- /dev/null +++ b/src/app/(dashboard)/dashboard/combos/KimiComboPresetCard.tsx @@ -0,0 +1,54 @@ +"use client"; + +import { useTranslations } from "next-intl"; +import { Card, Button } from "@/shared/components"; + +/** + * Kimi Coding combo preset (2026-07 partnership) — card UI. See + * kimiComboPreset.ts for the preset payload + why this exists instead of a + * generic template picker (there isn't one that can pin named providers). + * Styled with the Kimi accent, offered next to AutoComboCatalog on the + * combos page. POSTs via the exact same handleCreate() path as every other + * combo (mirrors handleDuplicate's directness — no separate confirmation + * step). + */ +interface KimiComboPresetCardProps { + /** True when a combo named KIMI_CODING_PRESET_NAME already exists — hides the card. */ + alreadyCreated: boolean; + creating: boolean; + onCreate: () => void; +} + +export default function KimiComboPresetCard({ + alreadyCreated, + creating, + onCreate, +}: KimiComboPresetCardProps) { + const t = useTranslations("combos"); + + if (alreadyCreated) return null; + + return ( + +
+
+
+ + bolt + +
+
+

{t("kimiPresetTitle")}

+

{t("kimiPresetDescription")}

+
+
+ +
+
+ ); +} diff --git a/src/app/(dashboard)/dashboard/combos/kimiComboPreset.ts b/src/app/(dashboard)/dashboard/combos/kimiComboPreset.ts new file mode 100644 index 00000000000..5d67eb20f78 --- /dev/null +++ b/src/app/(dashboard)/dashboard/combos/kimiComboPreset.ts @@ -0,0 +1,45 @@ +// Kimi Coding combo preset — pure data, split out of KimiComboPresetCard.tsx +// so it can be unit-tested with node:test (no DOM/next-intl needed), mirroring +// kimiSponsorBanner.ts. +// +// There is no generic preset/template picker in the combo builder today — +// AUTO_COMBO_TEMPLATES (src/domain/assessment/types.ts, surfaced by +// AutoComboCatalog.tsx) is a different mechanism: category/tier-scored +// zero-config routing, not a named provider+model pin, so it cannot express +// "kimi-k3 primary, kimi-coding/kimi-web fallback". KimiComboPresetCard.tsx is +// the minimal idiomatic addition instead: a one-click "create" card that POSTs +// this exact payload via the same handleCreate() path as every other combo. +// +// Model ids differ per provider even for the "same" underlying model: +// - moonshot (Moonshot API): "kimi-k3" +// - kimi-coding (OAuth coding plan): "k3" +// - kimi-web (web session): "k3" +// See open-sse/config/providers/registry/{moonshot,kimi/coding,kimi/web}. +export const KIMI_CODING_PRESET_NAME = "Kimi Coding"; + +export interface KimiComboPresetModel { + provider: string; + model: string; + weight: number; +} + +export interface KimiComboPreset { + name: string; + strategy: "priority"; + models: KimiComboPresetModel[]; +} + +export const KIMI_CODING_PRESET: KimiComboPreset = { + name: KIMI_CODING_PRESET_NAME, + strategy: "priority", + models: [ + { provider: "moonshot", model: "kimi-k3", weight: 100 }, + { provider: "kimi-coding", model: "k3", weight: 100 }, + { provider: "kimi-web", model: "k3", weight: 100 }, + ], +}; + +/** True when `combos` (as returned by GET /api/combos) already has the Kimi Coding preset. */ +export function hasKimiCodingPreset(combos: Array<{ name?: string | null }>): boolean { + return combos.some((combo) => combo?.name === KIMI_CODING_PRESET_NAME); +} diff --git a/src/app/(dashboard)/dashboard/combos/page.tsx b/src/app/(dashboard)/dashboard/combos/page.tsx index b99237d24a1..a92efe4e026 100644 --- a/src/app/(dashboard)/dashboard/combos/page.tsx +++ b/src/app/(dashboard)/dashboard/combos/page.tsx @@ -41,6 +41,8 @@ import { } from "@/lib/combos/builderDraft"; import { normalizeComboConfigMode } from "@/shared/constants/comboConfigMode"; import AutoComboCatalog from "./AutoComboCatalog"; +import KimiComboPresetCard from "./KimiComboPresetCard"; +import { KIMI_CODING_PRESET, hasKimiCodingPreset } from "./kimiComboPreset"; import BuilderIntelligentStep from "./BuilderIntelligentStep"; import IntelligentComboPanel from "./IntelligentComboPanel"; import { @@ -688,6 +690,7 @@ export default function CombosPage() { const [providerNodes, setProviderNodes] = useState([]); const [showUsageGuide, setShowUsageGuide] = useState(true); const [recentlyCreatedCombo, setRecentlyCreatedCombo] = useState(""); + const [creatingKimiPreset, setCreatingKimiPreset] = useState(false); const [comboDragIndex, setComboDragIndex] = useState(null); const [comboDragOverIndex, setComboDragOverIndex] = useState(null); const [savingComboOrder, setSavingComboOrder] = useState(false); @@ -858,6 +861,18 @@ export default function CombosPage() { await handleCreate(data); }; + // Kimi Coding preset (2026-07 partnership) — one-click create, mirrors + // handleDuplicate's directness (no separate confirmation modal). See + // KimiComboPresetCard.tsx for why this bypasses the combo builder wizard. + const handleCreateKimiPreset = async () => { + setCreatingKimiPreset(true); + try { + await handleCreate(KIMI_CODING_PRESET); + } finally { + setCreatingKimiPreset(false); + } + }; + const handleTestCombo = async (combo) => { setTestingCombo(combo.name); setTestResults(null); @@ -1040,6 +1055,12 @@ export default function CombosPage() { + + {showUsageGuide && ( setShowUsageGuide(false)} diff --git a/src/app/(dashboard)/dashboard/kimiSponsorBanner.ts b/src/app/(dashboard)/dashboard/kimiSponsorBanner.ts new file mode 100644 index 00000000000..22cd943e664 --- /dev/null +++ b/src/app/(dashboard)/dashboard/kimiSponsorBanner.ts @@ -0,0 +1,31 @@ +// Kimi (Moonshot AI) official-partnership sponsor banner — version gate. +// +// Pure logic split out of KimiSponsorBanner.tsx so the gate can be unit-tested +// with node:test (no DOM/next-intl needed), mirroring homeAppearance.ts. + +import { isNewer, normalizeVersion } from "@/lib/system/versionCheck"; + +/** + * Last app version that still shows the Kimi sponsor banner (inclusive). + * + * Kimi partnership: banner runs v3.8.49 through v3.8.60 (10 official releases + * counted from v3.8.50), capped at 3 months from 2026-07-21. + */ +export const KIMI_SPONSOR_BANNER_THROUGH_VERSION = "3.8.60"; + +/** + * Whether the Kimi sponsor banner should render for the given running app + * version. True while `currentVersion <= KIMI_SPONSOR_BANNER_THROUGH_VERSION` + * (semver comparison, reusing the same `isNewer`/`normalizeVersion` helpers as + * the "Update Available" banner). Fails safe (hides the banner) when the + * version string cannot be parsed at all, rather than defaulting to "always + * show" for a value like the `"unknown"` sentinel `getCurrentVersion()` can + * return if `package.json` cannot be read. + */ +export function shouldShowKimiSponsorBanner(currentVersion: string | null | undefined): boolean { + if (typeof currentVersion !== "string") return false; + if (!normalizeVersion(currentVersion)) return false; + // isNewer(a, b) is true iff a > b — so isNewer(current, THROUGH) is true + // exactly when current is past the sunset version. Negate for "<=". + return !isNewer(currentVersion, KIMI_SPONSOR_BANNER_THROUGH_VERSION); +} diff --git a/src/app/(dashboard)/dashboard/providers/[id]/components/ProviderPageHeader.tsx b/src/app/(dashboard)/dashboard/providers/[id]/components/ProviderPageHeader.tsx index fda56087f8b..622ef36e5f1 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/components/ProviderPageHeader.tsx +++ b/src/app/(dashboard)/dashboard/providers/[id]/components/ProviderPageHeader.tsx @@ -3,8 +3,9 @@ // Phase 1t.1 extraction — Issue #3501 import Link from "next/link"; import ProviderIcon from "@/shared/components/ProviderIcon"; -import { getHeaderIconProviderId } from "../providerPageHelpers"; +import { getHeaderIconProviderId, providerText } from "../providerPageHelpers"; import type { ProviderMessageTranslator } from "../providerPageHelpers"; +import { isKimiPartnerProviderId } from "../../featuredProviders"; interface ProviderInfo { id: string; @@ -37,6 +38,19 @@ export default function ProviderPageHeader({ onOpenTutorial, t, }: ProviderPageHeaderProps) { + // Kimi (Moonshot AI) official-partnership aff links (2026-07): the header + // website link doubles as the CTA for kimi-coding/kimi-web/moonshot's + // tracking links (see website field in oauth.ts / web-cookie.ts / + // apikey/regional.ts) — flag it with a discreet "Partner link" note so it + // reads as a monetized link, not just "visit provider website" like every + // other card. UI-only — never affects routing/fallback (featuredProviders.ts). + const isKimiPartnerLink = isKimiPartnerProviderId(providerInfo.id); + const kimiPartnerLinkNote = providerText( + t, + "kimiPartnerLinkNote", + "Partner link — supports OmniRoute at no extra cost to you" + ); + return (
{providerInfo.name} open_in_new @@ -85,6 +103,11 @@ export default function ProviderPageHeader({

{t("connectionCountLabel", { count: connectionsCount })}

+ {isKimiPartnerLink && providerInfo.website && ( + + {kimiPartnerLinkNote} + + )} {providerId === "adapta-web" && (