mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-14 10:52:17 +03:00
Validado em lote numa worktree combinada com os 6 PRs destas duas levas sobre o tip de `release/v3.8.51`: os seis boardaram **sem um único conflito**, `typecheck:core` limpo e **54/54** nos 7 arquivos de teste que trazem. O drift de `i18n:check` (`docs/security/GUARDRAILS.md`, `STEALTH_GUIDE.md` — source-changed) foi medido também no tip puro e é idêntico: base-red pré-existente, não desta leva.
This commit is contained in:
1
changelog.d/fixes/12371-kimi-k3-effort-tiers.md
Normal file
1
changelog.d/fixes/12371-kimi-k3-effort-tiers.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(models):** publish `effort_tiers` on Kimi K3's synced base-model entries (`kmca/k3`, `kmca/k3-256k`) so catalog-only clients (OpenCode, plain SDK pickers) can see and select the reasoning tiers (`low`/`high`/`max`) the synced metadata already carried — the `isSkippedEffortProvider` gate no longer suppresses tier visibility on those base entries, while synthetic `<id>-<tier>` variant generation stays prevented and Codex/GLM base models remain excluded unchanged ([#12299](https://github.com/diegosouzapw/OmniRoute/issues/12299))
|
||||
@@ -21,6 +21,12 @@
|
||||
* `<id>-<tier>` catalog entries (open-sse/utils/syncedEffortVariants.ts) — it
|
||||
* never runs over the base entry's `capabilities`, so it cannot substitute
|
||||
* for this check. Required (not optional) so no call site can silently skip it.
|
||||
*
|
||||
* #12299 carve-out: Kimi K3's synced base entries (`k3`, `k3-256k` — the kmca
|
||||
* catalog's `low`/`high`/`max` vocabulary) are exempted from the exclusion so
|
||||
* catalog-only clients (OpenCode, plain SDK pickers) can see and select their
|
||||
* tiers. Model-scoped, never provider-wide: Codex, GLM, and non-K3 kimi models
|
||||
* keep the full exclusion exactly as before this carve-out.
|
||||
*/
|
||||
// Use the same canonical alias as catalogModelPolicy.ts (l.1) — a relative path from
|
||||
// src/app/api/v1/models/ to open-sse/ would need 5 `../` and silently breaks under
|
||||
@@ -39,8 +45,30 @@ interface SyncedCapabilityFlags {
|
||||
supportedThinkingEfforts?: string[];
|
||||
}
|
||||
|
||||
// Model-id pattern for the Kimi K3 family (#12299): the kmca catalog syncs
|
||||
// `k3`/`k3-256k` (and prefixed forms such as `kmca/k3`). Same shape the
|
||||
// executor/translator layers use to recognize K3 elsewhere
|
||||
// (reasoningContentInjector.ts::K3_AUTHENTIC_REASONING_PATTERN).
|
||||
const KIMI_K3_MODEL_ID_PATTERN = /(?:^|\/)(?:kimi-)?k3(?:$|-)/i;
|
||||
|
||||
/**
|
||||
* #12299: only Kimi K3's synced BASE entries are exempt from the
|
||||
* `isSkippedEffortProvider` exclusion. Model-scoped, never provider-wide —
|
||||
* the exemption requires a kimi-owned provider AND a K3 model id, so Codex,
|
||||
* GLM, and non-K3 kimi models keep the exclusion contract from #7694.
|
||||
*/
|
||||
function isExemptKimiK3BaseModel(sm: SyncedCapabilityFlags, ownedBy: string): boolean {
|
||||
return (
|
||||
ownedBy.startsWith("kimi") && typeof sm.id === "string" && KIMI_K3_MODEL_ID_PATTERN.test(sm.id)
|
||||
);
|
||||
}
|
||||
|
||||
function effectiveEffortTiers(sm: SyncedCapabilityFlags, ownedBy: string): string[] | undefined {
|
||||
if (isSkippedEffortProvider(ownedBy)) return undefined;
|
||||
// Exclusion gate (#7694): codex/glm/kimi own a conflicting `-{effort}` suffix
|
||||
// mechanism — the blind opencode-plugin mapping must never see effort_tiers
|
||||
// for them, or it double-handles the suffix. #12299 narrows only the kimi K3
|
||||
// base-model entries out of that gate; everything else stays excluded.
|
||||
if (isSkippedEffortProvider(ownedBy) && !isExemptKimiK3BaseModel(sm, ownedBy)) return undefined;
|
||||
const learned = sm.id ? getLearnedReasoningEffortForModel(sm.id) : null;
|
||||
const synced =
|
||||
Array.isArray(sm.supportedThinkingEfforts) && sm.supportedThinkingEfforts.length > 0
|
||||
|
||||
103
tests/unit/kimi-k3-effort-tiers-12299.test.ts
Normal file
103
tests/unit/kimi-k3-effort-tiers-12299.test.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import path from "node:path";
|
||||
import {
|
||||
buildSyncedCapabilities,
|
||||
mergeSyncedCapabilities,
|
||||
} from "../../src/app/api/v1/models/syncedCapabilities.ts";
|
||||
import {
|
||||
shouldExposeSyncedEffortVariants,
|
||||
appendSyncedEffortVariants,
|
||||
} from "../../open-sse/utils/syncedEffortVariants.ts";
|
||||
|
||||
// #12299: Kimi K3's supportedThinkingEfforts (["low", "high", "max"]) were
|
||||
// suppressed on the BASE model by isSkippedEffortProvider in
|
||||
// effectiveEffortTiers(), leaving catalog-only clients with no tiers to copy.
|
||||
// Fix: publish effort_tiers on the base model while still preventing
|
||||
// synthetic <id>-<tier> variant generation for kimi providers.
|
||||
|
||||
const KIMI_K3_TIERS = ["low", "high", "max"];
|
||||
|
||||
test("Kimi K3 base model publishes effort_tiers via buildSyncedCapabilities (#12299)", () => {
|
||||
const caps = buildSyncedCapabilities(
|
||||
{ id: "k3", supportsThinking: true, supportedThinkingEfforts: KIMI_K3_TIERS },
|
||||
"kimi-coding-apikey"
|
||||
);
|
||||
assert.ok(caps, "capabilities must be defined for kimi K3");
|
||||
assert.deepEqual(
|
||||
caps.effort_tiers,
|
||||
KIMI_K3_TIERS,
|
||||
"kimi K3 base model must publish effort_tiers low/high/max"
|
||||
);
|
||||
});
|
||||
|
||||
test("Kimi K3-256k base model publishes effort_tiers via buildSyncedCapabilities (#12299)", () => {
|
||||
const caps = buildSyncedCapabilities(
|
||||
{ id: "k3-256k", supportsThinking: true, supportedThinkingEfforts: KIMI_K3_TIERS },
|
||||
"kimi-coding-apikey"
|
||||
);
|
||||
assert.ok(caps, "capabilities must be defined for kimi K3-256k");
|
||||
assert.deepEqual(
|
||||
caps.effort_tiers,
|
||||
KIMI_K3_TIERS,
|
||||
"kimi K3-256k base model must publish effort_tiers low/high/max"
|
||||
);
|
||||
});
|
||||
|
||||
test("Kimi K3 merge path also publishes effort_tiers (#12299)", () => {
|
||||
const merged = mergeSyncedCapabilities(
|
||||
{ tool_calling: true },
|
||||
{ id: "k3", supportsThinking: true, supportedThinkingEfforts: KIMI_K3_TIERS },
|
||||
"kimi-coding-apikey"
|
||||
);
|
||||
assert.ok(merged, "merged capabilities must be defined");
|
||||
assert.deepEqual(
|
||||
merged.effort_tiers,
|
||||
KIMI_K3_TIERS,
|
||||
"merge path must publish kimi K3 effort_tiers"
|
||||
);
|
||||
assert.equal(merged.tool_calling, true, "existing tool_calling must be preserved");
|
||||
});
|
||||
|
||||
test("shouldExposeSyncedEffortVariants still prevents synthetic kimi variants", () => {
|
||||
// The base model should NOT generate synthetic <id>-<tier> entries
|
||||
assert.equal(
|
||||
shouldExposeSyncedEffortVariants({
|
||||
id: "kimi/k3",
|
||||
owned_by: "kimi-coding-apikey",
|
||||
capabilities: { effort_tiers: KIMI_K3_TIERS },
|
||||
}),
|
||||
false,
|
||||
"must not generate synthetic kimi/k3-low, kimi/k3-high, etc."
|
||||
);
|
||||
});
|
||||
|
||||
test("appendSyncedEffortVariants does not create kimi variant entries", () => {
|
||||
const models = [
|
||||
{
|
||||
id: "kimi-coding-apikey/k3",
|
||||
owned_by: "kimi-coding-apikey",
|
||||
capabilities: { effort_tiers: KIMI_K3_TIERS },
|
||||
},
|
||||
];
|
||||
const result = appendSyncedEffortVariants(models);
|
||||
assert.equal(result.length, 1, "must not add synthetic variant entries for kimi");
|
||||
assert.equal(result[0].id, "kimi-coding-apikey/k3", "original entry must be unchanged");
|
||||
});
|
||||
|
||||
test("kimi K3 static registry tiers match synced metadata", () => {
|
||||
// Verify the static registry in runtime.ts has the correct tiers
|
||||
const runtimePath = path.join(
|
||||
path.dirname(fileURLToPath(import.meta.url)),
|
||||
"../../open-sse/config/providers/registry/kimi/coding/runtime.ts"
|
||||
);
|
||||
const content = readFileSync(runtimePath, "utf8");
|
||||
|
||||
// Verify the static thinking policies declare the same tiers
|
||||
assert.ok(
|
||||
content.includes('"low", "high", "max"'),
|
||||
"KIMI_CODE_STATIC_THINKING_POLICIES.k3 must declare low/high/max"
|
||||
);
|
||||
});
|
||||
@@ -62,6 +62,9 @@ test("merge path keeps vision AND applies the learned override", () => {
|
||||
// Exclusion gate (#7694): codex/glm/kimi already own a conflicting
|
||||
// `-{effort}` suffix mechanism — the blind opencode-plugin mapping must never
|
||||
// see effort_tiers for them, learned or synced, or it double-handles the suffix.
|
||||
// #12299 exempts only Kimi K3's BASE model entries (asserted in
|
||||
// tests/unit/kimi-k3-effort-tiers-12299.test.ts) — non-K3 kimi models such as
|
||||
// "excluded-model" below stay excluded alongside codex/glm.
|
||||
for (const ownedBy of ["codex", "glm", "glm-cn", "glmt", "kimi", "kimi-coding-apikey"]) {
|
||||
test(`build: excluded provider "${ownedBy}" never gets effort_tiers (synced)`, () => {
|
||||
const caps = buildSyncedCapabilities(
|
||||
|
||||
Reference in New Issue
Block a user