From f52c88f6a3abdb6f3ad9b013782923e2a6b8fb11 Mon Sep 17 00:00:00 2001 From: Felipe Teodoro Date: Fri, 7 Aug 2026 22:50:01 -0300 Subject: [PATCH] feat(ai): add Moonshot (Kimi) as an AI provider Adds 'moonshot' to the AiModelProvider union with four models: kimi-k3, kimi-k2.7-code, kimi-k2.7-code-highspeed, and kimi-k2.6. Routes through the workshop backend's openai-completions path against api.moonshot.ai/v1 because Moonshot is not a Cloudflare first-party provider (no AI Gateway route exists for it). Adds a local MOONSHOT_MODEL_COSTS lookup so that the missing pi-ai catalog entry doesn't fall back to ZERO_COST and skew BYOK accounting. Also wires up MOONSHOT_API_KEY/MOONSHOT_API_URL through run-dev-server for local dev defaults. --- packages/workshop-backend/src/ai-models.ts | 54 +++++++++++++++++++ .../src/chat-attachment-validation.ts | 1 + packages/workshop-backend/src/env.d.ts | 6 +++ packages/workshop-shared/src/api.ts | 13 ++++- run-dev-server.js | 2 + 5 files changed, 75 insertions(+), 1 deletion(-) diff --git a/packages/workshop-backend/src/ai-models.ts b/packages/workshop-backend/src/ai-models.ts index 25d1b759..40c8683f 100644 --- a/packages/workshop-backend/src/ai-models.ts +++ b/packages/workshop-backend/src/ai-models.ts @@ -115,6 +115,21 @@ const API_STREAMS: Record> = { const ZERO_COST: ModelCost = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }; +// Per-model Moonshot billing (USD per 1k tokens), reasoning capability, and input modalities. +// Source: https://platform.moonshot.ai/docs/pricing/chat — verify against live page before shipping. +// Used because Moonshot isn't in pi-ai's built-in catalog, so catalogModel() returns undefined +// and we look up locally to avoid falling back to ZERO_COST (which would skew BYOK accounting). +const MOONSHOT_MODEL_COSTS: Record = { + "kimi-k3": { cost: { input: 0.002, output: 0.008, cacheRead: 0, cacheWrite: 0 }, reasoning: true, input: ["text"] }, + "kimi-k2.7-code": { cost: { input: 0.0015, output: 0.006, cacheRead: 0, cacheWrite: 0 }, reasoning: true, input: ["text"] }, + "kimi-k2.7-code-highspeed": { cost: { input: 0.0015, output: 0.006, cacheRead: 0, cacheWrite: 0 }, reasoning: true, input: ["text"] }, + "kimi-k2.6": { cost: { input: 0.0006, output: 0.002, cacheRead: 0, cacheWrite: 0 }, reasoning: false, input: ["text"] }, +}; + // Consult pi's builtin catalog for cost/compat metadata of a known model id. Unknown models are // fine (synthesized with zero cost). Import per-provider, not providers/all. function catalogModel(provider: AiModelConfig["provider"], modelId: string): Model | undefined { @@ -124,6 +139,7 @@ function catalogModel(provider: AiModelConfig["provider"], modelId: string): Mod case "google": return (GOOGLE_MODELS as Record>)[modelId]; case "cloudflare": return (CLOUDFLARE_WORKERS_AI_MODELS as Record>)[modelId]; case "ollama": return undefined; + case "moonshot": return undefined; // Moonshot is OpenAI-compatible; pi doesn't ship a catalog. default: return undefined; } } @@ -231,6 +247,22 @@ function gatewayNativeModel(config: AiModelConfig, gatewayUrl: string): Model boolean>; function sanitizeChatAttachmentMimeType(mimeType: string | undefined): string { diff --git a/packages/workshop-backend/src/env.d.ts b/packages/workshop-backend/src/env.d.ts index 3077a96d..895e16c1 100644 --- a/packages/workshop-backend/src/env.d.ts +++ b/packages/workshop-backend/src/env.d.ts @@ -83,6 +83,12 @@ declare global { // Minimum connected-account balance (USD) to proceed via BYOK. Defaults to // MINIMUM_CLOUDFLARE_BALANCE. MINIMUM_CLOUDFLARE_BALANCE?: string; + + // Moonshot AI provider (Kimi). The workshop hits api.moonshot.ai directly because Moonshot + // is not a Cloudflare first-party provider (no AI Gateway route for it). These env vars + // configure direct-mode defaults; per-model configurations in the user DO override them. + MOONSHOT_API_KEY?: string; + MOONSHOT_API_URL?: string; } } } diff --git a/packages/workshop-shared/src/api.ts b/packages/workshop-shared/src/api.ts index 7cee2e78..aac2c46b 100644 --- a/packages/workshop-shared/src/api.ts +++ b/packages/workshop-shared/src/api.ts @@ -917,7 +917,7 @@ export type CloudflareAccountOption = { }; // Supported AI providers. -export type AiModelProvider = "openai" | "anthropic" | "google" | "cloudflare" | "ollama"; +export type AiModelProvider = "openai" | "anthropic" | "google" | "cloudflare" | "ollama" | "moonshot"; // Information about the AI gateway configuration. Returned by `AuthenticatedApi.getAiConfig()`. export type AiGatewayInfo = { @@ -985,6 +985,17 @@ export const SUGGESTED_MODELS: Record< }, "ollama": { }, + "moonshot": { + // Moonshot AI — Kimi models. Hosted at api.moonshot.ai via OpenAI-compatible + // /v1/chat/completions; the workshop hits it directly (bypassing AI Gateway since + // Moonshot is not a Cloudflare first-party provider). contextWindow values per + // https://platform.moonshot.ai/docs/pricing/chat (K3: 262144, K2.7-code: 262144, + // K2.6: 131072). + "kimi-k3": {name: "Kimi K3 (Moonshot)", contextWindow: 262144, outputLimit: 32768}, + "kimi-k2.7-code": {name: "Kimi K2.7 Code (Moonshot)", contextWindow: 262144, outputLimit: 32768}, + "kimi-k2.7-code-highspeed": {name: "Kimi K2.7 Code Highspeed (Moonshot)", contextWindow: 262144, outputLimit: 32768}, + "kimi-k2.6": {name: "Kimi K2.6 (Moonshot)", contextWindow: 131072, outputLimit: 8192}, + }, }; // Metadata about a workspace (one Overseer DO and everything in it). Includes everything needed diff --git a/run-dev-server.js b/run-dev-server.js index a5aba890..6c476e9f 100644 --- a/run-dev-server.js +++ b/run-dev-server.js @@ -246,6 +246,8 @@ for (const gk of gatekeepers) { // over HTTPS with tokens). "CF_AI_GATEWAY", "CF_AI_GATEWAY_PROVIDERS", "CF_AI_GATEWAY_ACCOUNT_ID", "CF_AI_GATEWAY_API_TOKEN", "CF_AI_GATEWAY_WAI", "CF_AI_GATEWAY_WAI_DIRECT", + // Moonshot AI provider (Kimi models). Direct OpenAI-compatible → api.moonshot.ai/v1. + "MOONSHOT_API_KEY", "MOONSHOT_API_URL", ]; // OAuth app credentials (GOOGLE_/GITHUB_/CLOUDFLARE_OAUTH_*) are NOT passed to the backend anymore; // they are injected into the gatekeeper Workers (see SHARED_GATEKEEPER_CREDS below).