Route Kimi K2.6 requests through CanopyWave#550
Conversation
Backend-only wiring. No agent or freebuff-model changes — current behavior is unchanged because nothing in the codebase requests moonshotai/kimi-k2.6 yet. Sets the stage for switching the freebuff "smart" model in a follow-up PR. - Add moonshotai/kimi-k2.6 to CANOPYWAVE_MODEL_MAP so isCanopyWaveModel picks it up. - Refactor canopywave pricing into a per-model map and add Kimi pricing ($0.60/$0.15/$2.50 per 1M in/cache/out, approximate Moonshot rates). - Flip useCanopyWave from `false` to isCanopyWaveModel(...) in _post.ts (stream + non-stream). For models not in the map this is a no-op — only minimax-m2.5 and kimi-k2.6 are affected, neither of which is currently used. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Greptile SummaryThis PR wires
Confidence Score: 4/5Safe to merge once the silent billing fallback in One P1 finding: the fallback to Kimi pricing for unrecognized models will silently compute wrong billing if the two maps ever diverge. The routing changes themselves are correct and the PR is currently a no-op in production, but fixing the fallback before merge prevents a harder-to-catch billing bug in follow-up PRs. web/src/llm-api/canopywave.ts — the
|
| Filename | Overview |
|---|---|
| web/src/llm-api/canopywave.ts | Adds Kimi K2.6 to the CanopyWave model map and introduces a per-model pricing map; the fallback to Kimi pricing in getCanopyWavePricing is a silent billing risk if the two parallel maps drift out of sync. |
| web/src/app/api/v1/chat/completions/_post.ts | Enables live CanopyWave routing by replacing the hardcoded false with isCanopyWaveModel(...) and correctly short-circuits Fireworks/OpenAI-Direct checks for both streaming and non-streaming paths. |
Prompt To Fix All With AI
This is a comment left during a code review.
Path: web/src/llm-api/canopywave.ts
Line: 109-111
Comment:
**Silent fallback to wrong pricing on map drift**
`getCanopyWavePricing` falls back to Kimi K2.6 pricing for any model that isn't in `CANOPYWAVE_PRICING_MAP`. Because `CANOPYWAVE_MODEL_MAP` and `CANOPYWAVE_PRICING_MAP` must be kept manually in sync, a future model added to only one of the two maps would silently bill at Kimi rates — either under-charging (MiniMax rates for Kimi) or over-charging (Kimi rates for a cheaper model). Since the model reaching this function is always a confirmed CanopyWave model, the fallback can be removed and replaced with an explicit error:
```ts
function getCanopyWavePricing(model: string): CanopyWavePricing {
const pricing = CANOPYWAVE_PRICING_MAP[model]
if (!pricing) throw new Error(`No CanopyWave pricing found for model: ${model}`)
return pricing
}
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: web/src/llm-api/canopywave.ts
Line: 29-107
Comment:
**Simplify: merge the two parallel maps into one**
`CANOPYWAVE_MODEL_MAP` and `CANOPYWAVE_PRICING_MAP` always need to be updated together, and the model IDs happen to be identical across both maps. Keeping them separate creates a drift risk (flagged above). Merging them into a single structure eliminates the redundancy and makes `isCanopyWaveModel`, `getCanopyWaveModelId`, and `getCanopyWavePricing` all hit the same source of truth:
```ts
const CANOPYWAVE_MODELS: Record<string, { canopywaveId: string; pricing: CanopyWavePricing }> = {
'minimax/minimax-m2.5': {
canopywaveId: 'minimax/minimax-m2.5',
pricing: { inputCostPerToken: 0.27 / 1_000_000, cachedInputCostPerToken: 0.03 / 1_000_000, outputCostPerToken: 1.08 / 1_000_000 },
},
'moonshotai/kimi-k2.6': {
canopywaveId: 'moonshotai/kimi-k2.6',
pricing: { inputCostPerToken: 0.60 / 1_000_000, cachedInputCostPerToken: 0.15 / 1_000_000, outputCostPerToken: 2.50 / 1_000_000 },
},
}
export function isCanopyWaveModel(model: string): boolean { return model in CANOPYWAVE_MODELS }
function getCanopyWaveModelId(m: string): string { return CANOPYWAVE_MODELS[m]?.canopywaveId ?? m }
function getCanopyWavePricing(model: string): CanopyWavePricing {
const entry = CANOPYWAVE_MODELS[model]
if (!entry) throw new Error(`No CanopyWave pricing found for model: ${model}`)
return entry.pricing
}
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "Route Kimi K2.6 requests through CanopyW..." | Re-trigger Greptile
| function getCanopyWavePricing(model: string): CanopyWavePricing { | ||
| return CANOPYWAVE_PRICING_MAP[model] ?? CANOPYWAVE_PRICING_MAP['moonshotai/kimi-k2.6'] | ||
| } |
There was a problem hiding this comment.
Silent fallback to wrong pricing on map drift
getCanopyWavePricing falls back to Kimi K2.6 pricing for any model that isn't in CANOPYWAVE_PRICING_MAP. Because CANOPYWAVE_MODEL_MAP and CANOPYWAVE_PRICING_MAP must be kept manually in sync, a future model added to only one of the two maps would silently bill at Kimi rates — either under-charging (MiniMax rates for Kimi) or over-charging (Kimi rates for a cheaper model). Since the model reaching this function is always a confirmed CanopyWave model, the fallback can be removed and replaced with an explicit error:
function getCanopyWavePricing(model: string): CanopyWavePricing {
const pricing = CANOPYWAVE_PRICING_MAP[model]
if (!pricing) throw new Error(`No CanopyWave pricing found for model: ${model}`)
return pricing
}Prompt To Fix With AI
This is a comment left during a code review.
Path: web/src/llm-api/canopywave.ts
Line: 109-111
Comment:
**Silent fallback to wrong pricing on map drift**
`getCanopyWavePricing` falls back to Kimi K2.6 pricing for any model that isn't in `CANOPYWAVE_PRICING_MAP`. Because `CANOPYWAVE_MODEL_MAP` and `CANOPYWAVE_PRICING_MAP` must be kept manually in sync, a future model added to only one of the two maps would silently bill at Kimi rates — either under-charging (MiniMax rates for Kimi) or over-charging (Kimi rates for a cheaper model). Since the model reaching this function is always a confirmed CanopyWave model, the fallback can be removed and replaced with an explicit error:
```ts
function getCanopyWavePricing(model: string): CanopyWavePricing {
const pricing = CANOPYWAVE_PRICING_MAP[model]
if (!pricing) throw new Error(`No CanopyWave pricing found for model: ${model}`)
return pricing
}
```
How can I resolve this? If you propose a fix, please make it concise.| @@ -85,12 +86,31 @@ function createCanopyWaveRequest(params: { | |||
| }) | |||
| } | |||
|
|
|||
| // CanopyWave per-token pricing (dollars per token) for MiniMax M2.5 | |||
| const CANOPYWAVE_INPUT_COST_PER_TOKEN = 0.27 / 1_000_000 | |||
| const CANOPYWAVE_CACHED_INPUT_COST_PER_TOKEN = 0.03 / 1_000_000 | |||
| const CANOPYWAVE_OUTPUT_COST_PER_TOKEN = 1.08 / 1_000_000 | |||
| // CanopyWave per-token pricing (dollars per token), keyed by OpenRouter model ID | |||
| interface CanopyWavePricing { | |||
| inputCostPerToken: number | |||
| cachedInputCostPerToken: number | |||
| outputCostPerToken: number | |||
| } | |||
|
|
|||
| const CANOPYWAVE_PRICING_MAP: Record<string, CanopyWavePricing> = { | |||
| 'minimax/minimax-m2.5': { | |||
| inputCostPerToken: 0.27 / 1_000_000, | |||
| cachedInputCostPerToken: 0.03 / 1_000_000, | |||
| outputCostPerToken: 1.08 / 1_000_000, | |||
| }, | |||
| 'moonshotai/kimi-k2.6': { | |||
| inputCostPerToken: 0.60 / 1_000_000, | |||
| cachedInputCostPerToken: 0.15 / 1_000_000, | |||
| outputCostPerToken: 2.50 / 1_000_000, | |||
| }, | |||
| } | |||
There was a problem hiding this comment.
Simplify: merge the two parallel maps into one
CANOPYWAVE_MODEL_MAP and CANOPYWAVE_PRICING_MAP always need to be updated together, and the model IDs happen to be identical across both maps. Keeping them separate creates a drift risk (flagged above). Merging them into a single structure eliminates the redundancy and makes isCanopyWaveModel, getCanopyWaveModelId, and getCanopyWavePricing all hit the same source of truth:
const CANOPYWAVE_MODELS: Record<string, { canopywaveId: string; pricing: CanopyWavePricing }> = {
'minimax/minimax-m2.5': {
canopywaveId: 'minimax/minimax-m2.5',
pricing: { inputCostPerToken: 0.27 / 1_000_000, cachedInputCostPerToken: 0.03 / 1_000_000, outputCostPerToken: 1.08 / 1_000_000 },
},
'moonshotai/kimi-k2.6': {
canopywaveId: 'moonshotai/kimi-k2.6',
pricing: { inputCostPerToken: 0.60 / 1_000_000, cachedInputCostPerToken: 0.15 / 1_000_000, outputCostPerToken: 2.50 / 1_000_000 },
},
}
export function isCanopyWaveModel(model: string): boolean { return model in CANOPYWAVE_MODELS }
function getCanopyWaveModelId(m: string): string { return CANOPYWAVE_MODELS[m]?.canopywaveId ?? m }
function getCanopyWavePricing(model: string): CanopyWavePricing {
const entry = CANOPYWAVE_MODELS[model]
if (!entry) throw new Error(`No CanopyWave pricing found for model: ${model}`)
return entry.pricing
}Prompt To Fix With AI
This is a comment left during a code review.
Path: web/src/llm-api/canopywave.ts
Line: 29-107
Comment:
**Simplify: merge the two parallel maps into one**
`CANOPYWAVE_MODEL_MAP` and `CANOPYWAVE_PRICING_MAP` always need to be updated together, and the model IDs happen to be identical across both maps. Keeping them separate creates a drift risk (flagged above). Merging them into a single structure eliminates the redundancy and makes `isCanopyWaveModel`, `getCanopyWaveModelId`, and `getCanopyWavePricing` all hit the same source of truth:
```ts
const CANOPYWAVE_MODELS: Record<string, { canopywaveId: string; pricing: CanopyWavePricing }> = {
'minimax/minimax-m2.5': {
canopywaveId: 'minimax/minimax-m2.5',
pricing: { inputCostPerToken: 0.27 / 1_000_000, cachedInputCostPerToken: 0.03 / 1_000_000, outputCostPerToken: 1.08 / 1_000_000 },
},
'moonshotai/kimi-k2.6': {
canopywaveId: 'moonshotai/kimi-k2.6',
pricing: { inputCostPerToken: 0.60 / 1_000_000, cachedInputCostPerToken: 0.15 / 1_000_000, outputCostPerToken: 2.50 / 1_000_000 },
},
}
export function isCanopyWaveModel(model: string): boolean { return model in CANOPYWAVE_MODELS }
function getCanopyWaveModelId(m: string): string { return CANOPYWAVE_MODELS[m]?.canopywaveId ?? m }
function getCanopyWavePricing(model: string): CanopyWavePricing {
const entry = CANOPYWAVE_MODELS[model]
if (!entry) throw new Error(`No CanopyWave pricing found for model: ${model}`)
return entry.pricing
}
```
How can I resolve this? If you propose a fix, please make it concise.Combine CANOPYWAVE_MODEL_MAP and CANOPYWAVE_PRICING_MAP into a single CANOPYWAVE_MODELS map keyed by OpenRouter model ID. Removes the silent Kimi-pricing fallback in getCanopyWavePricing — it now throws on unknown models, since callers are expected to gate on isCanopyWaveModel first. Eliminates the drift risk if a future model is added to one map but not the other. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Confirmed CanopyWave rates. Was using approximate Moonshot K2 numbers as a placeholder. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Why this is a no-op today
Test plan
Follow-up
🤖 Generated with Claude Code