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/ui/shell/replay.py
CHANGED
|
@@ -1,104 +1,169 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import asyncio
|
|
2
4
|
import contextlib
|
|
3
5
|
import getpass
|
|
6
|
+
from collections import deque
|
|
4
7
|
from collections.abc import Sequence
|
|
5
8
|
from dataclasses import dataclass
|
|
9
|
+
from pathlib import Path
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
import aiofiles
|
|
12
|
+
from kosong.message import Message
|
|
8
13
|
from kosong.tooling import ToolError, ToolOk
|
|
9
14
|
|
|
10
|
-
from kimi_cli.soul import StatusSnapshot
|
|
11
15
|
from kimi_cli.ui.shell.console import console
|
|
12
16
|
from kimi_cli.ui.shell.prompt import PROMPT_SYMBOL
|
|
13
17
|
from kimi_cli.ui.shell.visualize import visualize
|
|
14
|
-
from kimi_cli.utils.
|
|
18
|
+
from kimi_cli.utils.aioqueue import QueueShutDown
|
|
19
|
+
from kimi_cli.utils.logging import logger
|
|
20
|
+
from kimi_cli.utils.message import message_stringify
|
|
15
21
|
from kimi_cli.wire import Wire
|
|
16
|
-
from kimi_cli.wire.
|
|
17
|
-
|
|
18
|
-
|
|
22
|
+
from kimi_cli.wire.serde import WireMessageRecord
|
|
23
|
+
from kimi_cli.wire.types import (
|
|
24
|
+
Event,
|
|
25
|
+
StatusUpdate,
|
|
26
|
+
StepBegin,
|
|
27
|
+
TextPart,
|
|
28
|
+
ToolResult,
|
|
29
|
+
TurnBegin,
|
|
30
|
+
is_event,
|
|
31
|
+
)
|
|
19
32
|
|
|
20
|
-
|
|
33
|
+
MAX_REPLAY_TURNS = 5
|
|
21
34
|
|
|
22
35
|
|
|
23
36
|
@dataclass(slots=True)
|
|
24
|
-
class
|
|
37
|
+
class _ReplayTurn:
|
|
25
38
|
user_message: Message
|
|
26
|
-
events: list[
|
|
39
|
+
events: list[Event]
|
|
27
40
|
n_steps: int = 0
|
|
28
41
|
|
|
29
42
|
|
|
30
|
-
async def replay_recent_history(
|
|
43
|
+
async def replay_recent_history(
|
|
44
|
+
history: Sequence[Message],
|
|
45
|
+
*,
|
|
46
|
+
wire_file: Path | None = None,
|
|
47
|
+
) -> None:
|
|
31
48
|
"""
|
|
32
|
-
Replay the most recent user-initiated
|
|
49
|
+
Replay the most recent user-initiated turns from the provided message history or wire file.
|
|
33
50
|
"""
|
|
34
|
-
|
|
35
|
-
if
|
|
51
|
+
turns = await _build_replay_turns_from_wire(wire_file)
|
|
52
|
+
if not turns:
|
|
53
|
+
start_idx = _find_replay_start(history)
|
|
54
|
+
if start_idx is None:
|
|
55
|
+
return
|
|
56
|
+
turns = _build_replay_turns_from_history(history[start_idx:])
|
|
57
|
+
if not turns:
|
|
36
58
|
return
|
|
37
59
|
|
|
38
|
-
|
|
39
|
-
if not runs:
|
|
40
|
-
return
|
|
41
|
-
|
|
42
|
-
for run in runs:
|
|
60
|
+
for turn in turns:
|
|
43
61
|
wire = Wire()
|
|
44
|
-
console.print(f"{getpass.getuser()}{PROMPT_SYMBOL} {message_stringify(
|
|
62
|
+
console.print(f"{getpass.getuser()}{PROMPT_SYMBOL} {message_stringify(turn.user_message)}")
|
|
45
63
|
ui_task = asyncio.create_task(
|
|
46
|
-
visualize(wire.ui_side, initial_status=
|
|
64
|
+
visualize(wire.ui_side(merge=False), initial_status=StatusUpdate())
|
|
47
65
|
)
|
|
48
|
-
for event in
|
|
66
|
+
for event in turn.events:
|
|
49
67
|
wire.soul_side.send(event)
|
|
50
68
|
await asyncio.sleep(0) # yield to UI loop
|
|
51
69
|
wire.shutdown()
|
|
52
|
-
with contextlib.suppress(
|
|
70
|
+
with contextlib.suppress(QueueShutDown):
|
|
53
71
|
await ui_task
|
|
54
72
|
|
|
55
73
|
|
|
74
|
+
async def _build_replay_turns_from_wire(wire_file: Path | None) -> list[_ReplayTurn]:
|
|
75
|
+
if wire_file is None or not wire_file.exists():
|
|
76
|
+
return []
|
|
77
|
+
|
|
78
|
+
size = wire_file.stat().st_size
|
|
79
|
+
if size > 20 * 1024 * 1024:
|
|
80
|
+
logger.info(
|
|
81
|
+
"Wire file too large for replay, skipping: {file} ({size} bytes)",
|
|
82
|
+
file=wire_file,
|
|
83
|
+
size=size,
|
|
84
|
+
)
|
|
85
|
+
return []
|
|
86
|
+
|
|
87
|
+
turns: deque[_ReplayTurn] = deque(maxlen=MAX_REPLAY_TURNS)
|
|
88
|
+
try:
|
|
89
|
+
async with aiofiles.open(wire_file, encoding="utf-8") as f:
|
|
90
|
+
async for line in f:
|
|
91
|
+
line = line.strip()
|
|
92
|
+
if not line:
|
|
93
|
+
continue
|
|
94
|
+
try:
|
|
95
|
+
record = WireMessageRecord.model_validate_json(line)
|
|
96
|
+
wire_msg = record.to_wire_message()
|
|
97
|
+
except ValueError:
|
|
98
|
+
continue
|
|
99
|
+
|
|
100
|
+
if isinstance(wire_msg, TurnBegin):
|
|
101
|
+
turns.append(
|
|
102
|
+
_ReplayTurn(
|
|
103
|
+
user_message=Message(role="user", content=wire_msg.user_input),
|
|
104
|
+
events=[],
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
continue
|
|
108
|
+
|
|
109
|
+
if not is_event(wire_msg) or not turns:
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
current_turn = turns[-1]
|
|
113
|
+
if isinstance(wire_msg, StepBegin):
|
|
114
|
+
current_turn.n_steps = wire_msg.n
|
|
115
|
+
current_turn.events.append(wire_msg)
|
|
116
|
+
except Exception:
|
|
117
|
+
logger.exception("Failed to build replay turns from wire file {file}:", file=wire_file)
|
|
118
|
+
return []
|
|
119
|
+
return list(turns)
|
|
120
|
+
|
|
121
|
+
|
|
56
122
|
def _is_user_message(message: Message) -> bool:
|
|
57
123
|
# FIXME: should consider non-text tool call results which are sent as user messages
|
|
58
124
|
if message.role != "user":
|
|
59
125
|
return False
|
|
60
|
-
return not
|
|
126
|
+
return not message.extract_text().startswith("<system>CHECKPOINT")
|
|
61
127
|
|
|
62
128
|
|
|
63
129
|
def _find_replay_start(history: Sequence[Message]) -> int | None:
|
|
64
130
|
indices = [idx for idx, message in enumerate(history) if _is_user_message(message)]
|
|
65
131
|
if not indices:
|
|
66
132
|
return None
|
|
67
|
-
# only replay last
|
|
68
|
-
return indices[max(0, len(indices) -
|
|
133
|
+
# only replay last MAX_REPLAY_TURNS messages
|
|
134
|
+
return indices[max(0, len(indices) - MAX_REPLAY_TURNS)]
|
|
69
135
|
|
|
70
136
|
|
|
71
|
-
def
|
|
72
|
-
|
|
73
|
-
|
|
137
|
+
def _build_replay_turns_from_history(history: Sequence[Message]) -> list[_ReplayTurn]:
|
|
138
|
+
turns: list[_ReplayTurn] = []
|
|
139
|
+
current_turn: _ReplayTurn | None = None
|
|
74
140
|
for message in history:
|
|
75
141
|
if _is_user_message(message):
|
|
76
|
-
# start a new
|
|
77
|
-
if
|
|
78
|
-
|
|
79
|
-
|
|
142
|
+
# start a new turn
|
|
143
|
+
if current_turn is not None:
|
|
144
|
+
turns.append(current_turn)
|
|
145
|
+
current_turn = _ReplayTurn(user_message=message, events=[])
|
|
80
146
|
elif message.role == "assistant":
|
|
81
|
-
if
|
|
147
|
+
if current_turn is None:
|
|
82
148
|
continue
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
else:
|
|
88
|
-
current_run.events.extend(message.content)
|
|
89
|
-
current_run.events.extend(message.tool_calls or [])
|
|
149
|
+
current_turn.n_steps += 1
|
|
150
|
+
current_turn.events.append(StepBegin(n=current_turn.n_steps))
|
|
151
|
+
current_turn.events.extend(message.content)
|
|
152
|
+
current_turn.events.extend(message.tool_calls or [])
|
|
90
153
|
elif message.role == "tool":
|
|
91
|
-
if
|
|
154
|
+
if current_turn is None:
|
|
92
155
|
continue
|
|
93
156
|
assert message.tool_call_id is not None
|
|
94
|
-
if
|
|
157
|
+
if any(
|
|
95
158
|
isinstance(part, TextPart) and part.text.startswith("<system>ERROR")
|
|
96
159
|
for part in message.content
|
|
97
160
|
):
|
|
98
161
|
result = ToolError(message="", output="", brief="")
|
|
99
162
|
else:
|
|
100
163
|
result = ToolOk(output=message.content)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
164
|
+
current_turn.events.append(
|
|
165
|
+
ToolResult(tool_call_id=message.tool_call_id, return_value=result)
|
|
166
|
+
)
|
|
167
|
+
if current_turn is not None:
|
|
168
|
+
turns.append(current_turn)
|
|
169
|
+
return turns
|
kimi_cli/ui/shell/setup.py
CHANGED
|
@@ -1,52 +1,39 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import asyncio
|
|
2
4
|
from typing import TYPE_CHECKING, NamedTuple
|
|
3
5
|
|
|
4
|
-
import
|
|
6
|
+
from loguru import logger
|
|
5
7
|
from prompt_toolkit import PromptSession
|
|
6
8
|
from prompt_toolkit.shortcuts.choice_input import ChoiceInput
|
|
7
9
|
from pydantic import SecretStr
|
|
8
10
|
|
|
9
|
-
from kimi_cli.config import
|
|
11
|
+
from kimi_cli.config import (
|
|
12
|
+
LLMModel,
|
|
13
|
+
LLMProvider,
|
|
14
|
+
MoonshotFetchConfig,
|
|
15
|
+
MoonshotSearchConfig,
|
|
16
|
+
load_config,
|
|
17
|
+
save_config,
|
|
18
|
+
)
|
|
19
|
+
from kimi_cli.platforms import (
|
|
20
|
+
PLATFORMS,
|
|
21
|
+
ModelInfo,
|
|
22
|
+
Platform,
|
|
23
|
+
get_platform_by_name,
|
|
24
|
+
list_models,
|
|
25
|
+
managed_model_key,
|
|
26
|
+
managed_provider_key,
|
|
27
|
+
)
|
|
10
28
|
from kimi_cli.ui.shell.console import console
|
|
11
|
-
from kimi_cli.ui.shell.
|
|
12
|
-
from kimi_cli.utils.aiohttp import new_client_session
|
|
29
|
+
from kimi_cli.ui.shell.slash import registry
|
|
13
30
|
|
|
14
31
|
if TYPE_CHECKING:
|
|
15
|
-
from kimi_cli.ui.shell import
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
name: str
|
|
21
|
-
base_url: str
|
|
22
|
-
search_url: str | None = None
|
|
23
|
-
allowed_models: list[str] | None = None
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
_PLATFORMS = [
|
|
27
|
-
_Platform(
|
|
28
|
-
id="kimi-for-coding",
|
|
29
|
-
name="Kimi For Coding",
|
|
30
|
-
base_url="https://api.kimi.com/coding/v1",
|
|
31
|
-
search_url="https://api.kimi.com/coding/v1/search",
|
|
32
|
-
),
|
|
33
|
-
_Platform(
|
|
34
|
-
id="moonshot-cn",
|
|
35
|
-
name="Moonshot AI 开放平台 (moonshot.cn)",
|
|
36
|
-
base_url="https://api.moonshot.cn/v1",
|
|
37
|
-
allowed_models=["kimi-k2-turbo-preview", "kimi-k2-0905-preview", "kimi-k2-0711-preview"],
|
|
38
|
-
),
|
|
39
|
-
_Platform(
|
|
40
|
-
id="moonshot-ai",
|
|
41
|
-
name="Moonshot AI Open Platform (moonshot.ai)",
|
|
42
|
-
base_url="https://api.moonshot.ai/v1",
|
|
43
|
-
allowed_models=["kimi-k2-turbo-preview", "kimi-k2-0905-preview", "kimi-k2-0711-preview"],
|
|
44
|
-
),
|
|
45
|
-
]
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
@meta_command
|
|
49
|
-
async def setup(app: "ShellApp", args: list[str]):
|
|
32
|
+
from kimi_cli.ui.shell import Shell
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@registry.command
|
|
36
|
+
async def setup(app: Shell, args: str):
|
|
50
37
|
"""Setup Kimi CLI"""
|
|
51
38
|
result = await _setup()
|
|
52
39
|
if not result:
|
|
@@ -54,17 +41,26 @@ async def setup(app: "ShellApp", args: list[str]):
|
|
|
54
41
|
return
|
|
55
42
|
|
|
56
43
|
config = load_config()
|
|
57
|
-
|
|
44
|
+
provider_key = managed_provider_key(result.platform.id)
|
|
45
|
+
model_key = managed_model_key(result.platform.id, result.selected_model.id)
|
|
46
|
+
config.providers[provider_key] = LLMProvider(
|
|
58
47
|
type="kimi",
|
|
59
48
|
base_url=result.platform.base_url,
|
|
60
49
|
api_key=result.api_key,
|
|
61
50
|
)
|
|
62
|
-
config.models
|
|
63
|
-
provider
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
51
|
+
for key, model in list(config.models.items()):
|
|
52
|
+
if model.provider == provider_key:
|
|
53
|
+
del config.models[key]
|
|
54
|
+
for model_info in result.models:
|
|
55
|
+
capabilities = model_info.capabilities or None
|
|
56
|
+
config.models[managed_model_key(result.platform.id, model_info.id)] = LLMModel(
|
|
57
|
+
provider=provider_key,
|
|
58
|
+
model=model_info.id,
|
|
59
|
+
max_context_size=model_info.context_length,
|
|
60
|
+
capabilities=capabilities,
|
|
61
|
+
)
|
|
62
|
+
config.default_model = model_key
|
|
63
|
+
config.default_thinking = result.thinking
|
|
68
64
|
|
|
69
65
|
if result.platform.search_url:
|
|
70
66
|
config.services.moonshot_search = MoonshotSearchConfig(
|
|
@@ -72,6 +68,12 @@ async def setup(app: "ShellApp", args: list[str]):
|
|
|
72
68
|
api_key=result.api_key,
|
|
73
69
|
)
|
|
74
70
|
|
|
71
|
+
if result.platform.fetch_url:
|
|
72
|
+
config.services.moonshot_fetch = MoonshotFetchConfig(
|
|
73
|
+
base_url=result.platform.fetch_url,
|
|
74
|
+
api_key=result.api_key,
|
|
75
|
+
)
|
|
76
|
+
|
|
75
77
|
save_config(config)
|
|
76
78
|
console.print("[green]✓[/green] Kimi CLI has been setup! Reloading...")
|
|
77
79
|
await asyncio.sleep(1)
|
|
@@ -83,23 +85,27 @@ async def setup(app: "ShellApp", args: list[str]):
|
|
|
83
85
|
|
|
84
86
|
|
|
85
87
|
class _SetupResult(NamedTuple):
|
|
86
|
-
platform:
|
|
88
|
+
platform: Platform
|
|
87
89
|
api_key: SecretStr
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
selected_model: ModelInfo
|
|
91
|
+
models: list[ModelInfo]
|
|
92
|
+
thinking: bool
|
|
90
93
|
|
|
91
94
|
|
|
92
95
|
async def _setup() -> _SetupResult | None:
|
|
93
96
|
# select the API platform
|
|
94
97
|
platform_name = await _prompt_choice(
|
|
95
|
-
header="Select
|
|
96
|
-
choices=[platform.name for platform in
|
|
98
|
+
header="Select a platform (↑↓ navigate, Enter select, Ctrl+C cancel):",
|
|
99
|
+
choices=[platform.name for platform in PLATFORMS],
|
|
97
100
|
)
|
|
98
101
|
if not platform_name:
|
|
99
102
|
console.print("[red]No platform selected[/red]")
|
|
100
103
|
return None
|
|
101
104
|
|
|
102
|
-
platform =
|
|
105
|
+
platform = get_platform_by_name(platform_name)
|
|
106
|
+
if platform is None:
|
|
107
|
+
console.print("[red]Unknown platform[/red]")
|
|
108
|
+
return None
|
|
103
109
|
|
|
104
110
|
# enter the API key
|
|
105
111
|
api_key = await _prompt_text("Enter your API key", is_password=True)
|
|
@@ -107,51 +113,52 @@ async def _setup() -> _SetupResult | None:
|
|
|
107
113
|
return None
|
|
108
114
|
|
|
109
115
|
# list models
|
|
110
|
-
models_url = f"{platform.base_url}/models"
|
|
111
116
|
try:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
models_url,
|
|
116
|
-
headers={
|
|
117
|
-
"Authorization": f"Bearer {api_key}",
|
|
118
|
-
},
|
|
119
|
-
raise_for_status=True,
|
|
120
|
-
) as response,
|
|
121
|
-
):
|
|
122
|
-
resp_json = await response.json()
|
|
123
|
-
except aiohttp.ClientError as e:
|
|
117
|
+
models = await list_models(platform, api_key)
|
|
118
|
+
except Exception as e:
|
|
119
|
+
logger.error("Failed to get models: {error}", error=e)
|
|
124
120
|
console.print(f"[red]Failed to get models: {e}[/red]")
|
|
125
121
|
return None
|
|
126
122
|
|
|
127
|
-
model_dict = {model["id"]: model for model in resp_json["data"]}
|
|
128
|
-
|
|
129
123
|
# select the model
|
|
130
|
-
if
|
|
131
|
-
model_ids = [model["id"] for model in resp_json["data"]]
|
|
132
|
-
else:
|
|
133
|
-
id_set = set(model["id"] for model in resp_json["data"])
|
|
134
|
-
model_ids = [model_id for model_id in platform.allowed_models if model_id in id_set]
|
|
135
|
-
|
|
136
|
-
if not model_ids:
|
|
124
|
+
if not models:
|
|
137
125
|
console.print("[red]No models available for the selected platform[/red]")
|
|
138
126
|
return None
|
|
139
127
|
|
|
128
|
+
model_map = {model.id: model for model in models}
|
|
140
129
|
model_id = await _prompt_choice(
|
|
141
|
-
header="Select
|
|
142
|
-
choices=
|
|
130
|
+
header="Select a model (↑↓ navigate, Enter select, Ctrl+C cancel):",
|
|
131
|
+
choices=list(model_map),
|
|
143
132
|
)
|
|
144
133
|
if not model_id:
|
|
145
134
|
console.print("[red]No model selected[/red]")
|
|
146
135
|
return None
|
|
147
136
|
|
|
148
|
-
|
|
137
|
+
selected_model = model_map[model_id]
|
|
138
|
+
|
|
139
|
+
# Determine thinking mode based on model capabilities
|
|
140
|
+
capabilities = selected_model.capabilities
|
|
141
|
+
thinking: bool
|
|
142
|
+
|
|
143
|
+
if "always_thinking" in capabilities:
|
|
144
|
+
thinking = True
|
|
145
|
+
elif "thinking" in capabilities:
|
|
146
|
+
thinking_selection = await _prompt_choice(
|
|
147
|
+
header="Enable thinking mode? (↑↓ navigate, Enter select, Ctrl+C cancel):",
|
|
148
|
+
choices=["off", "on"],
|
|
149
|
+
)
|
|
150
|
+
if not thinking_selection:
|
|
151
|
+
return None
|
|
152
|
+
thinking = thinking_selection == "on"
|
|
153
|
+
else:
|
|
154
|
+
thinking = False
|
|
149
155
|
|
|
150
156
|
return _SetupResult(
|
|
151
157
|
platform=platform,
|
|
152
158
|
api_key=SecretStr(api_key),
|
|
153
|
-
|
|
154
|
-
|
|
159
|
+
selected_model=selected_model,
|
|
160
|
+
models=models,
|
|
161
|
+
thinking=thinking,
|
|
155
162
|
)
|
|
156
163
|
|
|
157
164
|
|
|
@@ -170,7 +177,7 @@ async def _prompt_choice(*, header: str, choices: list[str]) -> str | None:
|
|
|
170
177
|
|
|
171
178
|
|
|
172
179
|
async def _prompt_text(prompt: str, *, is_password: bool = False) -> str | None:
|
|
173
|
-
session = PromptSession()
|
|
180
|
+
session = PromptSession[str]()
|
|
174
181
|
try:
|
|
175
182
|
return str(
|
|
176
183
|
await session.prompt_async(
|
|
@@ -182,8 +189,8 @@ async def _prompt_text(prompt: str, *, is_password: bool = False) -> str | None:
|
|
|
182
189
|
return None
|
|
183
190
|
|
|
184
191
|
|
|
185
|
-
@
|
|
186
|
-
def reload(app:
|
|
192
|
+
@registry.command
|
|
193
|
+
def reload(app: Shell, args: str):
|
|
187
194
|
"""Reload configuration"""
|
|
188
195
|
from kimi_cli.cli import Reload
|
|
189
196
|
|