kimi-cli 0.44__py3-none-any.whl → 0.78__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of kimi-cli might be problematic. Click here for more details.
- kimi_cli/CHANGELOG.md +349 -40
- kimi_cli/__init__.py +6 -0
- kimi_cli/acp/AGENTS.md +91 -0
- kimi_cli/acp/__init__.py +13 -0
- kimi_cli/acp/convert.py +111 -0
- kimi_cli/acp/kaos.py +270 -0
- kimi_cli/acp/mcp.py +46 -0
- kimi_cli/acp/server.py +335 -0
- kimi_cli/acp/session.py +445 -0
- kimi_cli/acp/tools.py +158 -0
- kimi_cli/acp/types.py +13 -0
- kimi_cli/agents/default/agent.yaml +4 -4
- kimi_cli/agents/default/sub.yaml +2 -1
- kimi_cli/agents/default/system.md +79 -21
- kimi_cli/agents/okabe/agent.yaml +17 -0
- kimi_cli/agentspec.py +53 -25
- kimi_cli/app.py +180 -52
- kimi_cli/cli/__init__.py +595 -0
- kimi_cli/cli/__main__.py +8 -0
- kimi_cli/cli/info.py +63 -0
- kimi_cli/cli/mcp.py +349 -0
- kimi_cli/config.py +153 -17
- kimi_cli/constant.py +3 -0
- kimi_cli/exception.py +23 -2
- kimi_cli/flow/__init__.py +117 -0
- kimi_cli/flow/d2.py +376 -0
- kimi_cli/flow/mermaid.py +218 -0
- kimi_cli/llm.py +129 -23
- kimi_cli/metadata.py +32 -7
- kimi_cli/platforms.py +262 -0
- kimi_cli/prompts/__init__.py +2 -0
- kimi_cli/prompts/compact.md +4 -5
- kimi_cli/session.py +223 -31
- kimi_cli/share.py +2 -0
- kimi_cli/skill.py +145 -0
- kimi_cli/skills/kimi-cli-help/SKILL.md +55 -0
- kimi_cli/skills/skill-creator/SKILL.md +351 -0
- kimi_cli/soul/__init__.py +51 -20
- kimi_cli/soul/agent.py +213 -85
- kimi_cli/soul/approval.py +86 -17
- kimi_cli/soul/compaction.py +64 -53
- kimi_cli/soul/context.py +38 -5
- kimi_cli/soul/denwarenji.py +2 -0
- kimi_cli/soul/kimisoul.py +442 -60
- kimi_cli/soul/message.py +54 -54
- kimi_cli/soul/slash.py +72 -0
- kimi_cli/soul/toolset.py +387 -6
- kimi_cli/toad.py +74 -0
- kimi_cli/tools/AGENTS.md +5 -0
- kimi_cli/tools/__init__.py +42 -34
- kimi_cli/tools/display.py +25 -0
- kimi_cli/tools/dmail/__init__.py +10 -10
- kimi_cli/tools/dmail/dmail.md +11 -9
- kimi_cli/tools/file/__init__.py +1 -3
- kimi_cli/tools/file/glob.py +20 -23
- kimi_cli/tools/file/grep.md +1 -1
- kimi_cli/tools/file/{grep.py → grep_local.py} +51 -23
- kimi_cli/tools/file/read.md +24 -6
- kimi_cli/tools/file/read.py +134 -50
- kimi_cli/tools/file/replace.md +1 -1
- kimi_cli/tools/file/replace.py +36 -29
- kimi_cli/tools/file/utils.py +282 -0
- kimi_cli/tools/file/write.py +43 -22
- kimi_cli/tools/multiagent/__init__.py +7 -0
- kimi_cli/tools/multiagent/create.md +11 -0
- kimi_cli/tools/multiagent/create.py +50 -0
- kimi_cli/tools/{task/__init__.py → multiagent/task.py} +48 -53
- kimi_cli/tools/shell/__init__.py +120 -0
- kimi_cli/tools/{bash → shell}/bash.md +1 -2
- kimi_cli/tools/shell/powershell.md +25 -0
- kimi_cli/tools/test.py +4 -4
- kimi_cli/tools/think/__init__.py +2 -2
- kimi_cli/tools/todo/__init__.py +14 -8
- kimi_cli/tools/utils.py +64 -24
- kimi_cli/tools/web/fetch.py +68 -13
- kimi_cli/tools/web/search.py +10 -12
- kimi_cli/ui/acp/__init__.py +65 -412
- kimi_cli/ui/print/__init__.py +37 -49
- kimi_cli/ui/print/visualize.py +179 -0
- kimi_cli/ui/shell/__init__.py +141 -84
- kimi_cli/ui/shell/console.py +2 -0
- kimi_cli/ui/shell/debug.py +28 -23
- kimi_cli/ui/shell/keyboard.py +5 -1
- kimi_cli/ui/shell/prompt.py +220 -194
- kimi_cli/ui/shell/replay.py +111 -46
- kimi_cli/ui/shell/setup.py +89 -82
- kimi_cli/ui/shell/slash.py +422 -0
- kimi_cli/ui/shell/update.py +4 -2
- kimi_cli/ui/shell/usage.py +271 -0
- kimi_cli/ui/shell/visualize.py +574 -72
- kimi_cli/ui/wire/__init__.py +267 -0
- kimi_cli/ui/wire/jsonrpc.py +142 -0
- kimi_cli/ui/wire/protocol.py +1 -0
- kimi_cli/utils/__init__.py +0 -0
- kimi_cli/utils/aiohttp.py +2 -0
- kimi_cli/utils/aioqueue.py +72 -0
- kimi_cli/utils/broadcast.py +37 -0
- kimi_cli/utils/changelog.py +12 -7
- kimi_cli/utils/clipboard.py +12 -0
- kimi_cli/utils/datetime.py +37 -0
- kimi_cli/utils/environment.py +58 -0
- kimi_cli/utils/envvar.py +12 -0
- kimi_cli/utils/frontmatter.py +44 -0
- kimi_cli/utils/logging.py +7 -6
- kimi_cli/utils/message.py +9 -14
- kimi_cli/utils/path.py +99 -9
- kimi_cli/utils/pyinstaller.py +6 -0
- kimi_cli/utils/rich/__init__.py +33 -0
- kimi_cli/utils/rich/columns.py +99 -0
- kimi_cli/utils/rich/markdown.py +961 -0
- kimi_cli/utils/rich/markdown_sample.md +108 -0
- kimi_cli/utils/rich/markdown_sample_short.md +2 -0
- kimi_cli/utils/signals.py +2 -0
- kimi_cli/utils/slashcmd.py +124 -0
- kimi_cli/utils/string.py +2 -0
- kimi_cli/utils/term.py +168 -0
- kimi_cli/utils/typing.py +20 -0
- kimi_cli/wire/__init__.py +98 -29
- kimi_cli/wire/serde.py +45 -0
- kimi_cli/wire/types.py +299 -0
- kimi_cli-0.78.dist-info/METADATA +200 -0
- kimi_cli-0.78.dist-info/RECORD +135 -0
- kimi_cli-0.78.dist-info/entry_points.txt +4 -0
- kimi_cli/cli.py +0 -250
- kimi_cli/soul/runtime.py +0 -96
- kimi_cli/tools/bash/__init__.py +0 -99
- kimi_cli/tools/file/patch.md +0 -8
- kimi_cli/tools/file/patch.py +0 -143
- kimi_cli/tools/mcp.py +0 -85
- kimi_cli/ui/shell/liveview.py +0 -386
- kimi_cli/ui/shell/metacmd.py +0 -262
- kimi_cli/wire/message.py +0 -91
- kimi_cli-0.44.dist-info/METADATA +0 -188
- kimi_cli-0.44.dist-info/RECORD +0 -89
- kimi_cli-0.44.dist-info/entry_points.txt +0 -3
- /kimi_cli/tools/{task → multiagent}/task.md +0 -0
- {kimi_cli-0.44.dist-info → kimi_cli-0.78.dist-info}/WHEEL +0 -0
kimi_cli/acp/server.py
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import sys
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, NamedTuple
|
|
8
|
+
|
|
9
|
+
import acp
|
|
10
|
+
from kaos.path import KaosPath
|
|
11
|
+
|
|
12
|
+
from kimi_cli.acp.kaos import ACPKaos
|
|
13
|
+
from kimi_cli.acp.mcp import acp_mcp_servers_to_mcp_config
|
|
14
|
+
from kimi_cli.acp.session import ACPSession
|
|
15
|
+
from kimi_cli.acp.tools import replace_tools
|
|
16
|
+
from kimi_cli.acp.types import ACPContentBlock, MCPServer
|
|
17
|
+
from kimi_cli.app import KimiCLI
|
|
18
|
+
from kimi_cli.config import LLMModel, load_config, save_config
|
|
19
|
+
from kimi_cli.constant import NAME, VERSION
|
|
20
|
+
from kimi_cli.llm import create_llm, derive_model_capabilities
|
|
21
|
+
from kimi_cli.session import Session
|
|
22
|
+
from kimi_cli.soul.slash import registry as soul_slash_registry
|
|
23
|
+
from kimi_cli.soul.toolset import KimiToolset
|
|
24
|
+
from kimi_cli.utils.logging import logger
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ACPServer:
|
|
28
|
+
def __init__(self) -> None:
|
|
29
|
+
self.client_capabilities: acp.schema.ClientCapabilities | None = None
|
|
30
|
+
self.conn: acp.Client | None = None
|
|
31
|
+
self.sessions: dict[str, tuple[ACPSession, _ModelIDConv]] = {}
|
|
32
|
+
|
|
33
|
+
def on_connect(self, conn: acp.Client) -> None:
|
|
34
|
+
logger.info("ACP client connected")
|
|
35
|
+
self.conn = conn
|
|
36
|
+
|
|
37
|
+
async def initialize(
|
|
38
|
+
self,
|
|
39
|
+
protocol_version: int,
|
|
40
|
+
client_capabilities: acp.schema.ClientCapabilities | None = None,
|
|
41
|
+
client_info: acp.schema.Implementation | None = None,
|
|
42
|
+
**kwargs: Any,
|
|
43
|
+
) -> acp.InitializeResponse:
|
|
44
|
+
logger.info(
|
|
45
|
+
"ACP server initialized with protocol version: {version}, "
|
|
46
|
+
"client capabilities: {capabilities}, client info: {info}",
|
|
47
|
+
version=protocol_version,
|
|
48
|
+
capabilities=client_capabilities,
|
|
49
|
+
info=client_info,
|
|
50
|
+
)
|
|
51
|
+
self.client_capabilities = client_capabilities
|
|
52
|
+
|
|
53
|
+
# get command and args of current process for terminal-auth
|
|
54
|
+
command = sys.argv[0]
|
|
55
|
+
if command.endswith("kimi"):
|
|
56
|
+
args = []
|
|
57
|
+
else:
|
|
58
|
+
idx = sys.argv.index("kimi")
|
|
59
|
+
args = sys.argv[1 : idx + 1]
|
|
60
|
+
|
|
61
|
+
return acp.InitializeResponse(
|
|
62
|
+
protocol_version=protocol_version,
|
|
63
|
+
agent_capabilities=acp.schema.AgentCapabilities(
|
|
64
|
+
load_session=True,
|
|
65
|
+
prompt_capabilities=acp.schema.PromptCapabilities(
|
|
66
|
+
embedded_context=False, image=True, audio=False
|
|
67
|
+
),
|
|
68
|
+
mcp_capabilities=acp.schema.McpCapabilities(http=True, sse=False),
|
|
69
|
+
session_capabilities=acp.schema.SessionCapabilities(
|
|
70
|
+
list=acp.schema.SessionListCapabilities(),
|
|
71
|
+
),
|
|
72
|
+
),
|
|
73
|
+
auth_methods=[
|
|
74
|
+
acp.schema.AuthMethod(
|
|
75
|
+
id="setup",
|
|
76
|
+
name="Setup LLM with /setup slash command",
|
|
77
|
+
description=(
|
|
78
|
+
"Run `kimi` command in the terminal, "
|
|
79
|
+
"then send `/setup` command to complete the setup."
|
|
80
|
+
),
|
|
81
|
+
field_meta={
|
|
82
|
+
"terminal-auth": {
|
|
83
|
+
"command": command,
|
|
84
|
+
"args": args,
|
|
85
|
+
"label": "Kimi CLI Setup",
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
)
|
|
89
|
+
],
|
|
90
|
+
agent_info=acp.schema.Implementation(name=NAME, version=VERSION),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
async def new_session(
|
|
94
|
+
self, cwd: str, mcp_servers: list[MCPServer], **kwargs: Any
|
|
95
|
+
) -> acp.NewSessionResponse:
|
|
96
|
+
logger.info("Creating new session for working directory: {cwd}", cwd=cwd)
|
|
97
|
+
assert self.conn is not None, "ACP client not connected"
|
|
98
|
+
assert self.client_capabilities is not None, "ACP connection not initialized"
|
|
99
|
+
|
|
100
|
+
session = await Session.create(KaosPath.unsafe_from_local_path(Path(cwd)))
|
|
101
|
+
|
|
102
|
+
mcp_config = acp_mcp_servers_to_mcp_config(mcp_servers)
|
|
103
|
+
cli_instance = await KimiCLI.create(
|
|
104
|
+
session,
|
|
105
|
+
mcp_configs=[mcp_config],
|
|
106
|
+
)
|
|
107
|
+
config = cli_instance.soul.runtime.config
|
|
108
|
+
acp_kaos = ACPKaos(self.conn, session.id, self.client_capabilities)
|
|
109
|
+
acp_session = ACPSession(session.id, cli_instance, self.conn, kaos=acp_kaos)
|
|
110
|
+
model_id_conv = _ModelIDConv(config.default_model, config.default_thinking)
|
|
111
|
+
self.sessions[session.id] = (acp_session, model_id_conv)
|
|
112
|
+
|
|
113
|
+
if isinstance(cli_instance.soul.agent.toolset, KimiToolset):
|
|
114
|
+
replace_tools(
|
|
115
|
+
self.client_capabilities,
|
|
116
|
+
self.conn,
|
|
117
|
+
session.id,
|
|
118
|
+
cli_instance.soul.agent.toolset,
|
|
119
|
+
cli_instance.soul.runtime,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
available_commands = [
|
|
123
|
+
acp.schema.AvailableCommand(name=cmd.name, description=cmd.description)
|
|
124
|
+
for cmd in soul_slash_registry.list_commands()
|
|
125
|
+
]
|
|
126
|
+
asyncio.create_task(
|
|
127
|
+
self.conn.session_update(
|
|
128
|
+
session_id=session.id,
|
|
129
|
+
update=acp.schema.AvailableCommandsUpdate(
|
|
130
|
+
session_update="available_commands_update",
|
|
131
|
+
available_commands=available_commands,
|
|
132
|
+
),
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
return acp.NewSessionResponse(
|
|
136
|
+
session_id=session.id,
|
|
137
|
+
modes=acp.schema.SessionModeState(
|
|
138
|
+
available_modes=[
|
|
139
|
+
acp.schema.SessionMode(
|
|
140
|
+
id="default",
|
|
141
|
+
name="Default",
|
|
142
|
+
description="The default mode.",
|
|
143
|
+
),
|
|
144
|
+
],
|
|
145
|
+
current_mode_id="default",
|
|
146
|
+
),
|
|
147
|
+
models=acp.schema.SessionModelState(
|
|
148
|
+
available_models=_expand_llm_models(config.models),
|
|
149
|
+
current_model_id=model_id_conv.to_acp_model_id(),
|
|
150
|
+
),
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
async def load_session(
|
|
154
|
+
self, cwd: str, mcp_servers: list[MCPServer], session_id: str, **kwargs: Any
|
|
155
|
+
) -> None:
|
|
156
|
+
logger.info("Loading session: {id} for working directory: {cwd}", id=session_id, cwd=cwd)
|
|
157
|
+
assert self.conn is not None, "ACP client not connected"
|
|
158
|
+
assert self.client_capabilities is not None, "ACP connection not initialized"
|
|
159
|
+
|
|
160
|
+
if session_id in self.sessions:
|
|
161
|
+
logger.warning("Session already loaded: {id}", id=session_id)
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
work_dir = KaosPath.unsafe_from_local_path(Path(cwd))
|
|
165
|
+
session = await Session.find(work_dir, session_id)
|
|
166
|
+
if session is None:
|
|
167
|
+
logger.error(
|
|
168
|
+
"Session not found: {id} for working directory: {cwd}", id=session_id, cwd=cwd
|
|
169
|
+
)
|
|
170
|
+
raise acp.RequestError.invalid_params({"session_id": "Session not found"})
|
|
171
|
+
|
|
172
|
+
mcp_config = acp_mcp_servers_to_mcp_config(mcp_servers)
|
|
173
|
+
cli_instance = await KimiCLI.create(
|
|
174
|
+
session,
|
|
175
|
+
mcp_configs=[mcp_config],
|
|
176
|
+
)
|
|
177
|
+
config = cli_instance.soul.runtime.config
|
|
178
|
+
acp_kaos = ACPKaos(self.conn, session.id, self.client_capabilities)
|
|
179
|
+
acp_session = ACPSession(session.id, cli_instance, self.conn, kaos=acp_kaos)
|
|
180
|
+
model_id_conv = _ModelIDConv(config.default_model, config.default_thinking)
|
|
181
|
+
self.sessions[session.id] = (acp_session, model_id_conv)
|
|
182
|
+
|
|
183
|
+
if isinstance(cli_instance.soul.agent.toolset, KimiToolset):
|
|
184
|
+
replace_tools(
|
|
185
|
+
self.client_capabilities,
|
|
186
|
+
self.conn,
|
|
187
|
+
session.id,
|
|
188
|
+
cli_instance.soul.agent.toolset,
|
|
189
|
+
cli_instance.soul.runtime,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# TODO: replay session history?
|
|
193
|
+
|
|
194
|
+
async def list_sessions(
|
|
195
|
+
self, cursor: str | None = None, cwd: str | None = None, **kwargs: Any
|
|
196
|
+
) -> acp.schema.ListSessionsResponse:
|
|
197
|
+
logger.info("Listing sessions for working directory: {cwd}", cwd=cwd)
|
|
198
|
+
if cwd is None:
|
|
199
|
+
return acp.schema.ListSessionsResponse(sessions=[], next_cursor=None)
|
|
200
|
+
work_dir = KaosPath.unsafe_from_local_path(Path(cwd))
|
|
201
|
+
sessions = await Session.list(work_dir)
|
|
202
|
+
return acp.schema.ListSessionsResponse(
|
|
203
|
+
sessions=[
|
|
204
|
+
acp.schema.SessionInfo(
|
|
205
|
+
cwd=cwd,
|
|
206
|
+
session_id=s.id,
|
|
207
|
+
title=s.title,
|
|
208
|
+
updated_at=datetime.fromtimestamp(s.updated_at).astimezone().isoformat(),
|
|
209
|
+
)
|
|
210
|
+
for s in sessions
|
|
211
|
+
],
|
|
212
|
+
next_cursor=None,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
async def set_session_mode(self, mode_id: str, session_id: str, **kwargs: Any) -> None:
|
|
216
|
+
assert mode_id == "default", "Only default mode is supported"
|
|
217
|
+
|
|
218
|
+
async def set_session_model(self, model_id: str, session_id: str, **kwargs: Any) -> None:
|
|
219
|
+
logger.info(
|
|
220
|
+
"Setting session model to {model_id} for session: {id}",
|
|
221
|
+
model_id=model_id,
|
|
222
|
+
id=session_id,
|
|
223
|
+
)
|
|
224
|
+
if session_id not in self.sessions:
|
|
225
|
+
logger.error("Session not found: {id}", id=session_id)
|
|
226
|
+
raise acp.RequestError.invalid_params({"session_id": "Session not found"})
|
|
227
|
+
|
|
228
|
+
acp_session, current_model_id = self.sessions[session_id]
|
|
229
|
+
cli_instance = acp_session.cli
|
|
230
|
+
model_id_conv = _ModelIDConv.from_acp_model_id(model_id)
|
|
231
|
+
if model_id_conv == current_model_id:
|
|
232
|
+
return
|
|
233
|
+
|
|
234
|
+
config = cli_instance.soul.runtime.config
|
|
235
|
+
new_model = config.models.get(model_id_conv.model_key)
|
|
236
|
+
if new_model is None:
|
|
237
|
+
logger.error("Model not found: {model_key}", model_key=model_id_conv.model_key)
|
|
238
|
+
raise acp.RequestError.invalid_params({"model_id": "Model not found"})
|
|
239
|
+
new_provider = config.providers.get(new_model.provider)
|
|
240
|
+
if new_provider is None:
|
|
241
|
+
logger.error(
|
|
242
|
+
"Provider not found: {provider} for model: {model_key}",
|
|
243
|
+
provider=new_model.provider,
|
|
244
|
+
model_key=model_id_conv.model_key,
|
|
245
|
+
)
|
|
246
|
+
raise acp.RequestError.invalid_params({"model_id": "Model's provider not found"})
|
|
247
|
+
|
|
248
|
+
new_llm = create_llm(
|
|
249
|
+
new_provider,
|
|
250
|
+
new_model,
|
|
251
|
+
session_id=acp_session.id,
|
|
252
|
+
thinking=model_id_conv.thinking,
|
|
253
|
+
)
|
|
254
|
+
cli_instance.soul.runtime.llm = new_llm
|
|
255
|
+
|
|
256
|
+
config.default_model = model_id_conv.model_key
|
|
257
|
+
config.default_thinking = model_id_conv.thinking
|
|
258
|
+
assert config.is_from_default_location, "`kimi acp` must use the default config location"
|
|
259
|
+
config_for_save = load_config()
|
|
260
|
+
config_for_save.default_model = model_id_conv.model_key
|
|
261
|
+
config_for_save.default_thinking = model_id_conv.thinking
|
|
262
|
+
save_config(config_for_save)
|
|
263
|
+
|
|
264
|
+
async def authenticate(self, method_id: str, **kwargs: Any) -> acp.AuthenticateResponse | None:
|
|
265
|
+
raise NotImplementedError
|
|
266
|
+
|
|
267
|
+
async def prompt(
|
|
268
|
+
self, prompt: list[ACPContentBlock], session_id: str, **kwargs: Any
|
|
269
|
+
) -> acp.PromptResponse:
|
|
270
|
+
logger.info("Received prompt request for session: {id}", id=session_id)
|
|
271
|
+
if session_id not in self.sessions:
|
|
272
|
+
logger.error("Session not found: {id}", id=session_id)
|
|
273
|
+
raise acp.RequestError.invalid_params({"session_id": "Session not found"})
|
|
274
|
+
acp_session, *_ = self.sessions[session_id]
|
|
275
|
+
return await acp_session.prompt(prompt)
|
|
276
|
+
|
|
277
|
+
async def cancel(self, session_id: str, **kwargs: Any) -> None:
|
|
278
|
+
logger.info("Received cancel request for session: {id}", id=session_id)
|
|
279
|
+
if session_id not in self.sessions:
|
|
280
|
+
logger.error("Session not found: {id}", id=session_id)
|
|
281
|
+
raise acp.RequestError.invalid_params({"session_id": "Session not found"})
|
|
282
|
+
acp_session, *_ = self.sessions[session_id]
|
|
283
|
+
await acp_session.cancel()
|
|
284
|
+
|
|
285
|
+
async def ext_method(self, method: str, params: dict[str, Any]) -> dict[str, Any]:
|
|
286
|
+
raise NotImplementedError
|
|
287
|
+
|
|
288
|
+
async def ext_notification(self, method: str, params: dict[str, Any]) -> None:
|
|
289
|
+
raise NotImplementedError
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class _ModelIDConv(NamedTuple):
|
|
293
|
+
model_key: str
|
|
294
|
+
thinking: bool
|
|
295
|
+
|
|
296
|
+
@classmethod
|
|
297
|
+
def from_acp_model_id(cls, model_id: str) -> _ModelIDConv:
|
|
298
|
+
if model_id.endswith(",thinking"):
|
|
299
|
+
return _ModelIDConv(model_id[: -len(",thinking")], True)
|
|
300
|
+
return _ModelIDConv(model_id, False)
|
|
301
|
+
|
|
302
|
+
def to_acp_model_id(self) -> str:
|
|
303
|
+
if self.thinking:
|
|
304
|
+
return f"{self.model_key},thinking"
|
|
305
|
+
return self.model_key
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _expand_llm_models(models: dict[str, LLMModel]) -> list[acp.schema.ModelInfo]:
|
|
309
|
+
expanded_models: list[acp.schema.ModelInfo] = []
|
|
310
|
+
for model_key, model in models.items():
|
|
311
|
+
capabilities = derive_model_capabilities(model)
|
|
312
|
+
if "thinking" in model.model or "reason" in model.model:
|
|
313
|
+
# always-thinking models
|
|
314
|
+
expanded_models.append(
|
|
315
|
+
acp.schema.ModelInfo(
|
|
316
|
+
model_id=_ModelIDConv(model_key, True).to_acp_model_id(),
|
|
317
|
+
name=f"{model.model}",
|
|
318
|
+
)
|
|
319
|
+
)
|
|
320
|
+
else:
|
|
321
|
+
expanded_models.append(
|
|
322
|
+
acp.schema.ModelInfo(
|
|
323
|
+
model_id=model_key,
|
|
324
|
+
name=model.model,
|
|
325
|
+
)
|
|
326
|
+
)
|
|
327
|
+
if "thinking" in capabilities:
|
|
328
|
+
# add thinking variant
|
|
329
|
+
expanded_models.append(
|
|
330
|
+
acp.schema.ModelInfo(
|
|
331
|
+
model_id=_ModelIDConv(model_key, True).to_acp_model_id(),
|
|
332
|
+
name=f"{model.model} (thinking)",
|
|
333
|
+
)
|
|
334
|
+
)
|
|
335
|
+
return expanded_models
|