1515 * `for model_key, model in models.items()`.
1616 *
1717 * `thinkingSupported` is true if any of:
18- * 1. the alias's declared `capabilities` array contains `'thinking'`, or
18+ * 1. the alias's declared `capabilities` array contains `'thinking'`
19+ * (including the capability inferred from the Anthropic wire protocol —
20+ * see the `anthropicCompatible` context below), or
1921 * 2. the underlying model name matches `/thinking|reason/i`
2022 * (always-thinking variants), or
2123 * 3. the underlying model name is on the {@link TOGGLEABLE_THINKING_MODELS}
2224 * allow-list (mirrors `kimi-cli/src/kimi_cli/llm.py:derive_model_capabilities`).
25+ *
26+ * The runtime resolves a model's wire protocol from
27+ * `alias.protocol ?? provider.type` (see
28+ * `ProviderManager.resolveProviderConfig`). The derive helpers below take the
29+ * provider-derived `anthropicCompatible` flag as an optional second argument
30+ * so the catalog agrees with the runtime about Anthropic profiles even when
31+ * the alias itself does not declare `protocol`.
2332 */
2433
2534import { effectiveModelAlias } from '@moonshot-ai/agent-core' ;
@@ -55,8 +64,8 @@ export interface AcpModelEntry {
5564 */
5665const TOGGLEABLE_THINKING_MODELS = new Set ( [ 'kimi-for-coding' , 'kimi-code' ] ) ;
5766
58- export function deriveThinkingSupported ( alias : ModelAlias ) : boolean {
59- const effective = effectiveModelAlias ( alias ) ;
67+ export function deriveThinkingSupported ( alias : ModelAlias , anthropicCompatible = false ) : boolean {
68+ const effective = effectiveModelAlias ( alias , anthropicCompatible ) ;
6069 const declared = effective . capabilities ?? [ ] ;
6170 if ( declared . includes ( 'thinking' ) || declared . includes ( 'always_thinking' ) ) return true ;
6271 const lower = effective . model . toLowerCase ( ) ;
@@ -72,17 +81,22 @@ export function deriveThinkingSupported(alias: ModelAlias): boolean {
7281 * `thinkingSupported`, but only an explicit (server-derived) declaration
7382 * may remove the off option from the client.
7483 */
75- export function deriveAlwaysThinking ( alias : ModelAlias ) : boolean {
76- return ( effectiveModelAlias ( alias ) . capabilities ?? [ ] ) . includes ( 'always_thinking' ) ;
84+ export function deriveAlwaysThinking ( alias : ModelAlias , anthropicCompatible = false ) : boolean {
85+ return ( effectiveModelAlias ( alias , anthropicCompatible ) . capabilities ?? [ ] ) . includes (
86+ 'always_thinking' ,
87+ ) ;
7788}
7889
7990/**
8091 * The effort a boolean "thinking on" toggle maps to for this model: declared
8192 * `default_effort`, else the middle `support_efforts` entry, else `'on'` for
8293 * boolean models (no `support_efforts`).
8394 */
84- export function deriveDefaultThinkingEffort ( alias : ModelAlias ) : string {
85- const effective = effectiveModelAlias ( alias ) ;
95+ export function deriveDefaultThinkingEffort (
96+ alias : ModelAlias ,
97+ anthropicCompatible = false ,
98+ ) : string {
99+ const effective = effectiveModelAlias ( alias , anthropicCompatible ) ;
86100 const efforts = effective . supportEfforts ;
87101 if ( efforts !== undefined && efforts . length > 0 ) {
88102 return effective . defaultEffort ?? efforts [ Math . floor ( efforts . length / 2 ) ] ! ;
@@ -102,24 +116,45 @@ export async function listModelsFromHarness(
102116 harness : KimiHarness ,
103117) : Promise < readonly AcpModelEntry [ ] > {
104118 if ( typeof harness . getConfig !== 'function' ) return [ ] ;
105- let models : Record < string , ModelAlias > | undefined ;
119+ let config : Awaited < ReturnType < KimiHarness [ 'getConfig' ] > > ;
106120 try {
107- const config = await harness . getConfig ( ) ;
108- models = config . models ;
121+ config = await harness . getConfig ( ) ;
109122 } catch {
110123 return [ ] ;
111124 }
125+ const models = config . models ;
112126 if ( models === undefined ) return [ ] ;
113127 const out : AcpModelEntry [ ] = [ ] ;
114128 for ( const [ id , alias ] of Object . entries ( models ) ) {
115- const effective = effectiveModelAlias ( alias ) ;
129+ const anthropicCompatible = usesAnthropicProvider ( alias , config ) ;
130+ const effective = effectiveModelAlias ( alias , anthropicCompatible ) ;
116131 out . push ( {
117132 id,
118133 name : effective . displayName ?? effective . model ?? id ,
119- thinkingSupported : deriveThinkingSupported ( alias ) ,
120- alwaysThinking : deriveAlwaysThinking ( alias ) ,
121- defaultThinkingEffort : deriveDefaultThinkingEffort ( alias ) ,
134+ thinkingSupported : deriveThinkingSupported ( alias , anthropicCompatible ) ,
135+ alwaysThinking : deriveAlwaysThinking ( alias , anthropicCompatible ) ,
136+ defaultThinkingEffort : deriveDefaultThinkingEffort ( alias , anthropicCompatible ) ,
122137 } ) ;
123138 }
124139 return out ;
125140}
141+
142+ /**
143+ * Provider-level Anthropic context for an alias, mirroring how
144+ * `ProviderManager.resolveProviderConfig` resolves the wire protocol: the
145+ * alias's provider (falling back to the configured default provider) decides
146+ * when the alias itself does not declare `protocol`. Without this the catalog
147+ * would mark a custom-named model on an `type = "anthropic"` provider as not
148+ * thinking-capable while the runtime infers the latest Anthropic profile.
149+ */
150+ function usesAnthropicProvider (
151+ alias : ModelAlias ,
152+ config : {
153+ providers ?: Record < string , { type ?: string } | undefined > ;
154+ defaultProvider ?: string | undefined ;
155+ } ,
156+ ) : boolean {
157+ const providerName = alias . provider ?? config . defaultProvider ;
158+ if ( providerName === undefined ) return false ;
159+ return config . providers ?. [ providerName ] ?. type === 'anthropic' ;
160+ }
0 commit comments