File size: 3,740 Bytes
f0634fb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | /**
* ACP model catalog — projects the engine's model catalog (`IModelCatalog`,
* surfaced through `klient.global.kosong.listModels()`) into a flat list of
* selectable models for the ACP `configOptions` picker.
*
* ACP-specific heuristics (thinking-capability derivation, the toggleable-models
* allow-list) stay scoped to this host. Order mirrors the catalog's
* enumeration order (the engine's model-registry insertion order).
*
* `thinkingSupported` is true if any of:
* 1. the model's declared `capabilities` contains `'thinking'`/`'always_thinking'`, or
* 2. the model id matches `/thinking|reason/i`, or
* 3. the model id is on the {@link TOGGLEABLE_THINKING_MODELS} allow-list.
*/
import type { ModelCatalogItem } from '@moonshot-ai/klient';
/**
* One catalog row per configured model, suitable for an ACP picker.
*/
export interface AcpModelEntry {
readonly id: string;
readonly name: string;
readonly description?: string | undefined;
readonly thinkingSupported: boolean;
/** Declared 'always_thinking' capability — thinking cannot be turned off. */
readonly alwaysThinking?: boolean;
/**
* The thinking effort to send when the binary ACP toggle flips on: the
* model's declared `defaultEffort`, else the middle `supportEfforts` entry,
* else `'on'` for boolean models.
*/
readonly defaultThinkingEffort: string;
/**
* The model's declared selectable effort levels (`support_efforts`).
* `undefined` when the model only exposes a boolean thinking toggle.
*/
readonly supportEfforts?: readonly string[];
}
/**
* Models that support thinking by toggle (not by name match or capability
* declaration). ACP-picker-specific UX.
*/
const TOGGLEABLE_THINKING_MODELS = new Set(['kimi-for-coding', 'kimi-code']);
export function deriveThinkingSupported(item: ModelCatalogItem): boolean {
const capabilities = item.capabilities ?? [];
if (capabilities.includes('thinking') || capabilities.includes('always_thinking')) return true;
const lower = item.model.toLowerCase();
if (lower.includes('thinking') || lower.includes('reason')) return true;
if (TOGGLEABLE_THINKING_MODELS.has(item.model)) return true;
return false;
}
/**
* Whether the model declares the 'always_thinking' capability — thinking cannot
* be disabled, so the ACP toggle must lock to on. Capability-only by design.
*/
export function deriveAlwaysThinking(item: ModelCatalogItem): boolean {
return (item.capabilities ?? []).includes('always_thinking');
}
/**
* The effort a boolean "thinking on" toggle maps to for this model: declared
* `default_effort`, else the middle `support_efforts` entry, else `'on'`.
*/
export function deriveDefaultThinkingEffort(item: ModelCatalogItem): string {
const efforts = item.support_efforts;
if (efforts !== undefined && efforts.length > 0) {
return item.default_effort ?? efforts[Math.floor(efforts.length / 2)]!;
}
return 'on';
}
/**
* Project the engine's model catalog into a flat ACP catalog. Returns an empty
* array when no models are configured. The catalog item's `model` field is the
* model-registry id — the value `agent.setModel()` takes — so it doubles as
* the ACP picker value.
*/
export function projectModelCatalog(
items: readonly ModelCatalogItem[],
): readonly AcpModelEntry[] {
return items.map((item) => ({
id: item.model,
name: item.display_name ?? item.model,
thinkingSupported: deriveThinkingSupported(item),
alwaysThinking: deriveAlwaysThinking(item),
defaultThinkingEffort: deriveDefaultThinkingEffort(item),
supportEfforts:
item.support_efforts !== undefined && item.support_efforts.length > 0
? item.support_efforts
: undefined,
}));
}
|