yaicli 0.6.4__tar.gz → 0.7.0__tar.gz

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.
Files changed (50) hide show
  1. {yaicli-0.6.4 → yaicli-0.7.0}/PKG-INFO +91 -4
  2. {yaicli-0.6.4 → yaicli-0.7.0}/README.md +90 -3
  3. {yaicli-0.6.4 → yaicli-0.7.0}/pyproject.toml +1 -1
  4. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/const.py +17 -6
  5. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/entry.py +24 -1
  6. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/functions/__init__.py +13 -1
  7. yaicli-0.7.0/yaicli/llms/client.py +153 -0
  8. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/llms/providers/ai21_provider.py +33 -1
  9. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/llms/providers/chatglm_provider.py +30 -7
  10. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/llms/providers/doubao_provider.py +0 -14
  11. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/llms/providers/gemini_provider.py +21 -22
  12. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/llms/providers/huggingface_provider.py +1 -1
  13. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/llms/providers/openai_provider.py +17 -8
  14. yaicli-0.7.0/yaicli/tools/__init__.py +127 -0
  15. yaicli-0.7.0/yaicli/tools/function.py +90 -0
  16. yaicli-0.7.0/yaicli/tools/mcp.py +459 -0
  17. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/utils.py +34 -0
  18. yaicli-0.6.4/yaicli/llms/client.py +0 -120
  19. yaicli-0.6.4/yaicli/tools.py +0 -159
  20. {yaicli-0.6.4 → yaicli-0.7.0}/.gitignore +0 -0
  21. {yaicli-0.6.4 → yaicli-0.7.0}/LICENSE +0 -0
  22. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/__init__.py +0 -0
  23. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/chat.py +0 -0
  24. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/cli.py +0 -0
  25. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/config.py +0 -0
  26. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/console.py +0 -0
  27. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/exceptions.py +0 -0
  28. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/functions/buildin/execute_shell_command.py +0 -0
  29. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/history.py +0 -0
  30. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/llms/__init__.py +0 -0
  31. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/llms/provider.py +0 -0
  32. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/llms/providers/chutes_provider.py +0 -0
  33. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/llms/providers/cohere_provider.py +0 -0
  34. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/llms/providers/deepseek_provider.py +0 -0
  35. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/llms/providers/groq_provider.py +0 -0
  36. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/llms/providers/infiniai_provider.py +0 -0
  37. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/llms/providers/minimax_provider.py +0 -0
  38. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/llms/providers/modelscope_provider.py +0 -0
  39. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/llms/providers/ollama_provider.py +0 -0
  40. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/llms/providers/openrouter_provider.py +0 -0
  41. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/llms/providers/sambanova_provider.py +0 -0
  42. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/llms/providers/siliconflow_provider.py +0 -0
  43. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/llms/providers/targon_provider.py +0 -0
  44. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/llms/providers/vertexai_provider.py +0 -0
  45. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/llms/providers/xai_provider.py +0 -0
  46. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/llms/providers/yi_provider.py +0 -0
  47. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/printer.py +0 -0
  48. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/render.py +0 -0
  49. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/role.py +0 -0
  50. {yaicli-0.6.4 → yaicli-0.7.0}/yaicli/schemas.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yaicli
3
- Version: 0.6.4
3
+ Version: 0.7.0
4
4
  Summary: A simple CLI tool to interact with LLM
5
5
  Project-URL: Homepage, https://github.com/belingud/yaicli
6
6
  Project-URL: Repository, https://github.com/belingud/yaicli
@@ -265,6 +265,7 @@ generate and execute shell commands, or get quick answers without leaving your w
265
265
  > [!NOTE]
266
266
  > YAICLI is actively developed. While core functionality is stable, some features may evolve in future releases.
267
267
 
268
+ > We support MCP since v0.7.0!
268
269
  > We support Function Call since v0.5.0!
269
270
 
270
271
  ## ✨ Key Features
@@ -362,7 +363,6 @@ pip install .
362
363
  - Deepseek
363
364
  - Doubao
364
365
  - Gemini
365
- - Vertex AI
366
366
  - Groq
367
367
  - Huggingface
368
368
  - Minimax
@@ -372,6 +372,7 @@ pip install .
372
372
  - Sambanova
373
373
  - Siliconflow
374
374
  - Targon
375
+ - Vertex ai
375
376
  - X AI
376
377
  - Yi
377
378
  - Unlimited OpenAI-compatible providers
@@ -438,6 +439,10 @@ ROLE_MODIFY_WARNING=true
438
439
  ENABLE_FUNCTIONS=true
439
440
  # Set to false to disable showing function output in the response
440
441
  SHOW_FUNCTION_OUTPUT=true
442
+
443
+ # MCP settings
444
+ ENABLE_MCP=false
445
+ SHOW_MCP_OUTPUT=false
441
446
  ```
442
447
 
443
448
  ### Configuration Options Reference
@@ -468,7 +473,10 @@ SHOW_FUNCTION_OUTPUT=true
468
473
  | `MAX_SAVED_CHATS` | Max saved chats | `20` | `YAI_MAX_SAVED_CHATS` |
469
474
  | `ROLE_MODIFY_WARNING` | Warn user when modifying role | `true` | `YAI_ROLE_MODIFY_WARNING` |
470
475
  | `ENABLE_FUNCTIONS` | Enable function calling | `true` | `YAI_ENABLE_FUNCTIONS` |
471
- | `SHOW_FUNCTION_OUTPUT` | Show function output in response | `true` | `YAI_SHOW_FUNCTION_OUTPUT` |
476
+ | `SHOW_FUNCTION_OUTPUT` | Show function output when calling function | `true` | `YAI_SHOW_FUNCTION_OUTPUT` |
477
+ | `ENABLE_MCP` | Enable MCP tools | `false` | `YAI_ENABLE_MCP` |
478
+ | `SHOW_MCP_OUTPUT` | Show MCP output when calling mcp | `true` | `YAI_SHOW_MCP_OUTPUT` |
479
+
472
480
 
473
481
  ### LLM Provider Configuration
474
482
 
@@ -571,8 +579,10 @@ LOCATION=
571
579
 
572
580
  #### Huggingface
573
581
 
582
+ Default `HF_PROVIDER` is `auto`.
583
+
574
584
  ```ini
575
- HF_PROVIDER=sambanova
585
+ HF_PROVIDER=auto
576
586
  PROVIDER=huggingface
577
587
  API_KEY=
578
588
  MODEL=deepseek-ai/DeepSeek-R1-0528
@@ -1252,6 +1262,83 @@ Thinking:
1252
1262
  Current directory size: 156M (using du -sh .).
1253
1263
  ```
1254
1264
 
1265
+ ### MCP
1266
+
1267
+ Add your MCP config in `~/.config/yaicli/mcp.json` (`C:\Users\<user>\.config\yaicli\mcp.json` on Windows.).
1268
+
1269
+ `--enable-mcp` option is corresponds to the configuration key `ENABLE_MCP`.
1270
+
1271
+ Example:
1272
+
1273
+ ```shell
1274
+ ai 'What is the latest exchange rate between the BTC and the US dollar?' --enable-mcp --show-mcp-output
1275
+
1276
+ Assistant:
1277
+
1278
+ @Mcp call: bing_search({"query": "latest exchange rate between BTC and US dollar"})
1279
+ ╭─ Mcp output ──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
1280
+ │ [ │
1281
+ │ { │
1282
+ │ "id": "result_1751024997243_0", │
1283
+ │ "title": "BTC to USD - Bitcoin to US Dollar Conversion - Exchange Rates", │
1284
+ │ "link": "https://www.exchange-rates.org/converter/btc-usd", │
1285
+ │ "snippet": "11 小时之前 · 1 Bitcoin = 107,304 US Dollars as of June 27, 2025 03:00 AM UTC. You can get live exchange │
1286
+ │ rates between Bitcoin and US Dollars using exchange-rates.org, which aggregates …" │
1287
+ │ }, │
1288
+ │ { │
1289
+ │ "id": "result_1751024997245_1", │
1290
+ │ "title": "Live Bitcoin to US Dollars Exchange Rate - ₿ 1 …", │
1291
+ │ "link": "https://btc.currencyrate.today/usd", │
1292
+ │ "snippet": ".b_imgcap_altitle p strong,.b_imgcap_altitle .b_factrow strong{color:#767676}#b_results │
1293
+ │ .b_imgcap_altitle{line-height:22px}.b_hList img{display:block}..." │
1294
+ │ }, │
1295
+ │ { │
1296
+ │ "id": "result_1751024997246_2", │
1297
+ │ "title": "1 BTC to USD - Bitcoins to US Dollars Exchange Rate - Xe", │
1298
+ │ "link": "https://www.xe.com/currencyconverter/convert/?From=BTC&To=USD", │
1299
+ │ "snippet": "2025年6月15日 · Get the latest 1 Bitcoin to US Dollar rate for FREE with the original Universal Currency │
1300
+ │ Converter. Set rate alerts for to and learn more about Bitcoins and US Dollars from …" │
1301
+ │ }, │
1302
+ │ { │
1303
+ │ "id": "result_1751024997246_3", │
1304
+ │ "title": "BTC to USD Exchange Rates | Best Exchange Rates", │
1305
+ │ "link": "https://bestexchangerates.com/rates/btc-to-usd", │
1306
+ │ "snippet": "Bitcoin (BTC) to US dollar (USD) market data - latest interbank exchange rate, trend, chart & historic │
1307
+ │ rates. Sell BTC → Buy USD" │
1308
+ │ }, │
1309
+ │ { │
1310
+ │ "id": "result_1751024997247_4", │
1311
+ │ "title": "BTC to USD | Bitcoin to US Dollar - Investing.com", │
1312
+ │ "link": "https://www.investing.com/crypto/bitcoin/btc-usd", │
1313
+ │ "snippet": "Bitcoin Eyes 120k as Fed Rate Cuts Hopes Rise, US Dollar Falls to Multi-Year Lows BTC hovers around │
1314
+ │ 107.5k after attempts at 108k Fed rate cut optimism rises USD falls to its lowest level …" │
1315
+ │ } │
1316
+ │ ] │
1317
+ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
1318
+ Here are some current exchange rates for Bitcoin (BTC) to US Dollar (USD):
1319
+
1320
+ 1 Exchange-Rates.org:
1321
+ ₿1 Bitcoin = 💵107,304 US Dollars (as of June 27, 2025, 03:00 AM UTC).
1322
+ Link
1323
+ 2 BTC.CurrencyRate.Today:
1324
+ Live Bitcoin to US Dollars exchange rate.
1325
+ Link
1326
+ 3 Xe.com:
1327
+ Latest conversion rate and information about Bitcoin to US Dollars.
1328
+ Link
1329
+ 4 BestExchangeRates.com:
1330
+ Current BTC to USD market data, including charts and historic rates.
1331
+ Link
1332
+ 5 Investing.com:
1333
+ Bitcoin price analysis and live BTC to USD updates.
1334
+ Link
1335
+
1336
+ For the most accurate and up-to-date rate, I recommend checking one of these sources directly.
1337
+ ```
1338
+
1339
+ ![mcp](artwork/mcp_example.png)
1340
+
1341
+
1255
1342
  ## 💻 Technical Details
1256
1343
 
1257
1344
  ### Architecture
@@ -22,6 +22,7 @@ generate and execute shell commands, or get quick answers without leaving your w
22
22
  > [!NOTE]
23
23
  > YAICLI is actively developed. While core functionality is stable, some features may evolve in future releases.
24
24
 
25
+ > We support MCP since v0.7.0!
25
26
  > We support Function Call since v0.5.0!
26
27
 
27
28
  ## ✨ Key Features
@@ -119,7 +120,6 @@ pip install .
119
120
  - Deepseek
120
121
  - Doubao
121
122
  - Gemini
122
- - Vertex AI
123
123
  - Groq
124
124
  - Huggingface
125
125
  - Minimax
@@ -129,6 +129,7 @@ pip install .
129
129
  - Sambanova
130
130
  - Siliconflow
131
131
  - Targon
132
+ - Vertex ai
132
133
  - X AI
133
134
  - Yi
134
135
  - Unlimited OpenAI-compatible providers
@@ -195,6 +196,10 @@ ROLE_MODIFY_WARNING=true
195
196
  ENABLE_FUNCTIONS=true
196
197
  # Set to false to disable showing function output in the response
197
198
  SHOW_FUNCTION_OUTPUT=true
199
+
200
+ # MCP settings
201
+ ENABLE_MCP=false
202
+ SHOW_MCP_OUTPUT=false
198
203
  ```
199
204
 
200
205
  ### Configuration Options Reference
@@ -225,7 +230,10 @@ SHOW_FUNCTION_OUTPUT=true
225
230
  | `MAX_SAVED_CHATS` | Max saved chats | `20` | `YAI_MAX_SAVED_CHATS` |
226
231
  | `ROLE_MODIFY_WARNING` | Warn user when modifying role | `true` | `YAI_ROLE_MODIFY_WARNING` |
227
232
  | `ENABLE_FUNCTIONS` | Enable function calling | `true` | `YAI_ENABLE_FUNCTIONS` |
228
- | `SHOW_FUNCTION_OUTPUT` | Show function output in response | `true` | `YAI_SHOW_FUNCTION_OUTPUT` |
233
+ | `SHOW_FUNCTION_OUTPUT` | Show function output when calling function | `true` | `YAI_SHOW_FUNCTION_OUTPUT` |
234
+ | `ENABLE_MCP` | Enable MCP tools | `false` | `YAI_ENABLE_MCP` |
235
+ | `SHOW_MCP_OUTPUT` | Show MCP output when calling mcp | `true` | `YAI_SHOW_MCP_OUTPUT` |
236
+
229
237
 
230
238
  ### LLM Provider Configuration
231
239
 
@@ -328,8 +336,10 @@ LOCATION=
328
336
 
329
337
  #### Huggingface
330
338
 
339
+ Default `HF_PROVIDER` is `auto`.
340
+
331
341
  ```ini
332
- HF_PROVIDER=sambanova
342
+ HF_PROVIDER=auto
333
343
  PROVIDER=huggingface
334
344
  API_KEY=
335
345
  MODEL=deepseek-ai/DeepSeek-R1-0528
@@ -1009,6 +1019,83 @@ Thinking:
1009
1019
  Current directory size: 156M (using du -sh .).
1010
1020
  ```
1011
1021
 
1022
+ ### MCP
1023
+
1024
+ Add your MCP config in `~/.config/yaicli/mcp.json` (`C:\Users\<user>\.config\yaicli\mcp.json` on Windows.).
1025
+
1026
+ `--enable-mcp` option is corresponds to the configuration key `ENABLE_MCP`.
1027
+
1028
+ Example:
1029
+
1030
+ ```shell
1031
+ ai 'What is the latest exchange rate between the BTC and the US dollar?' --enable-mcp --show-mcp-output
1032
+
1033
+ Assistant:
1034
+
1035
+ @Mcp call: bing_search({"query": "latest exchange rate between BTC and US dollar"})
1036
+ ╭─ Mcp output ──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
1037
+ │ [ │
1038
+ │ { │
1039
+ │ "id": "result_1751024997243_0", │
1040
+ │ "title": "BTC to USD - Bitcoin to US Dollar Conversion - Exchange Rates", │
1041
+ │ "link": "https://www.exchange-rates.org/converter/btc-usd", │
1042
+ │ "snippet": "11 小时之前 · 1 Bitcoin = 107,304 US Dollars as of June 27, 2025 03:00 AM UTC. You can get live exchange │
1043
+ │ rates between Bitcoin and US Dollars using exchange-rates.org, which aggregates …" │
1044
+ │ }, │
1045
+ │ { │
1046
+ │ "id": "result_1751024997245_1", │
1047
+ │ "title": "Live Bitcoin to US Dollars Exchange Rate - ₿ 1 …", │
1048
+ │ "link": "https://btc.currencyrate.today/usd", │
1049
+ │ "snippet": ".b_imgcap_altitle p strong,.b_imgcap_altitle .b_factrow strong{color:#767676}#b_results │
1050
+ │ .b_imgcap_altitle{line-height:22px}.b_hList img{display:block}..." │
1051
+ │ }, │
1052
+ │ { │
1053
+ │ "id": "result_1751024997246_2", │
1054
+ │ "title": "1 BTC to USD - Bitcoins to US Dollars Exchange Rate - Xe", │
1055
+ │ "link": "https://www.xe.com/currencyconverter/convert/?From=BTC&To=USD", │
1056
+ │ "snippet": "2025年6月15日 · Get the latest 1 Bitcoin to US Dollar rate for FREE with the original Universal Currency │
1057
+ │ Converter. Set rate alerts for to and learn more about Bitcoins and US Dollars from …" │
1058
+ │ }, │
1059
+ │ { │
1060
+ │ "id": "result_1751024997246_3", │
1061
+ │ "title": "BTC to USD Exchange Rates | Best Exchange Rates", │
1062
+ │ "link": "https://bestexchangerates.com/rates/btc-to-usd", │
1063
+ │ "snippet": "Bitcoin (BTC) to US dollar (USD) market data - latest interbank exchange rate, trend, chart & historic │
1064
+ │ rates. Sell BTC → Buy USD" │
1065
+ │ }, │
1066
+ │ { │
1067
+ │ "id": "result_1751024997247_4", │
1068
+ │ "title": "BTC to USD | Bitcoin to US Dollar - Investing.com", │
1069
+ │ "link": "https://www.investing.com/crypto/bitcoin/btc-usd", │
1070
+ │ "snippet": "Bitcoin Eyes 120k as Fed Rate Cuts Hopes Rise, US Dollar Falls to Multi-Year Lows BTC hovers around │
1071
+ │ 107.5k after attempts at 108k Fed rate cut optimism rises USD falls to its lowest level …" │
1072
+ │ } │
1073
+ │ ] │
1074
+ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
1075
+ Here are some current exchange rates for Bitcoin (BTC) to US Dollar (USD):
1076
+
1077
+ 1 Exchange-Rates.org:
1078
+ ₿1 Bitcoin = 💵107,304 US Dollars (as of June 27, 2025, 03:00 AM UTC).
1079
+ Link
1080
+ 2 BTC.CurrencyRate.Today:
1081
+ Live Bitcoin to US Dollars exchange rate.
1082
+ Link
1083
+ 3 Xe.com:
1084
+ Latest conversion rate and information about Bitcoin to US Dollars.
1085
+ Link
1086
+ 4 BestExchangeRates.com:
1087
+ Current BTC to USD market data, including charts and historic rates.
1088
+ Link
1089
+ 5 Investing.com:
1090
+ Bitcoin price analysis and live BTC to USD updates.
1091
+ Link
1092
+
1093
+ For the most accurate and up-to-date rate, I recommend checking one of these sources directly.
1094
+ ```
1095
+
1096
+ ![mcp](artwork/mcp_example.png)
1097
+
1098
+
1012
1099
  ## 💻 Technical Details
1013
1100
 
1014
1101
  ### Architecture
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "yaicli"
3
- version = "0.6.4"
3
+ version = "0.7.0"
4
4
  description = "A simple CLI tool to interact with LLM"
5
5
  authors = [{ name = "belingud", email = "im.victor@qq.com" }]
6
6
  readme = "README.md"
@@ -16,7 +16,7 @@ from rich.console import JustifyMethod
16
16
  BOOL_STR = Literal["true", "false", "yes", "no", "y", "n", "1", "0", "on", "off"]
17
17
 
18
18
 
19
- class JustifyEnum(StrEnum):
19
+ class JustifyEnum(StrEnum): # type: ignore
20
20
  DEFAULT = "default"
21
21
  LEFT = "left"
22
22
  CENTER = "center"
@@ -43,6 +43,7 @@ HISTORY_FILE = Path("~/.yaicli_history").expanduser()
43
43
  CONFIG_PATH = Path("~/.config/yaicli/config.ini").expanduser()
44
44
  ROLES_DIR = CONFIG_PATH.parent / "roles"
45
45
  FUNCTIONS_DIR = CONFIG_PATH.parent / "functions"
46
+ MCP_JSON_PATH = CONFIG_PATH.parent / "mcp.json"
46
47
 
47
48
  # Default configuration values
48
49
  DEFAULT_CODE_THEME = "monokai"
@@ -69,6 +70,8 @@ DEFAULT_ROLE_MODIFY_WARNING: BOOL_STR = "true"
69
70
  DEFAULT_ENABLE_FUNCTIONS: BOOL_STR = "true"
70
71
  DEFAULT_SHOW_FUNCTION_OUTPUT: BOOL_STR = "true"
71
72
  DEFAULT_REASONING_EFFORT: Optional[Literal["low", "high", "medium"]] = None
73
+ DEFAULT_ENABLE_MCP: BOOL_STR = "false"
74
+ DEFAULT_SHOW_MCP_OUTPUT: BOOL_STR = "false"
72
75
 
73
76
 
74
77
  SHELL_PROMPT = """You are YAICLI, a shell command generator.
@@ -93,16 +96,16 @@ CODER_PROMPT = (
93
96
  )
94
97
 
95
98
 
96
- class DefaultRoleNames(StrEnum):
99
+ class DefaultRoleNames(StrEnum): # type: ignore
97
100
  SHELL = "Shell Command Generator"
98
101
  DEFAULT = "DEFAULT"
99
102
  CODER = "Code Assistant"
100
103
 
101
104
 
102
105
  DEFAULT_ROLES: dict[str, dict[str, Any]] = {
103
- DefaultRoleNames.SHELL.value: {"name": DefaultRoleNames.SHELL.value, "prompt": SHELL_PROMPT},
104
- DefaultRoleNames.DEFAULT.value: {"name": DefaultRoleNames.DEFAULT.value, "prompt": DEFAULT_PROMPT},
105
- DefaultRoleNames.CODER.value: {"name": DefaultRoleNames.CODER.value, "prompt": CODER_PROMPT},
106
+ DefaultRoleNames.SHELL.value: {"name": DefaultRoleNames.SHELL.value, "prompt": SHELL_PROMPT}, # type: ignore
107
+ DefaultRoleNames.DEFAULT.value: {"name": DefaultRoleNames.DEFAULT.value, "prompt": DEFAULT_PROMPT}, # type: ignore
108
+ DefaultRoleNames.CODER.value: {"name": DefaultRoleNames.CODER.value, "prompt": CODER_PROMPT}, # type: ignore
106
109
  }
107
110
 
108
111
  # DEFAULT_CONFIG_MAP is a dictionary of the configuration options.
@@ -151,6 +154,8 @@ DEFAULT_CONFIG_MAP = {
151
154
  "env_key": "YAI_SHOW_FUNCTION_OUTPUT",
152
155
  "type": bool,
153
156
  },
157
+ "ENABLE_MCP": {"value": DEFAULT_ENABLE_MCP, "env_key": "YAI_ENABLE_MCP", "type": bool},
158
+ "SHOW_MCP_OUTPUT": {"value": DEFAULT_SHOW_MCP_OUTPUT, "env_key": "YAI_SHOW_MCP_OUTPUT", "type": bool},
154
159
  }
155
160
 
156
161
  DEFAULT_CONFIG_INI = f"""[core]
@@ -201,6 +206,12 @@ ROLE_MODIFY_WARNING={DEFAULT_CONFIG_MAP["ROLE_MODIFY_WARNING"]["value"]}
201
206
  # Function settings
202
207
  # Set to false to disable sending functions in API requests
203
208
  ENABLE_FUNCTIONS={DEFAULT_CONFIG_MAP["ENABLE_FUNCTIONS"]["value"]}
204
- # Set to false to disable showing function output in the response
209
+ # Set to false to disable showing function output when calling functions
205
210
  SHOW_FUNCTION_OUTPUT={DEFAULT_CONFIG_MAP["SHOW_FUNCTION_OUTPUT"]["value"]}
211
+
212
+ # MCP settings
213
+ # Set to false to disable MCP in API requests
214
+ ENABLE_MCP={DEFAULT_CONFIG_MAP["ENABLE_MCP"]["value"]}
215
+ # Set to false to disable showing MCP output when calling MCP tools
216
+ SHOW_MCP_OUTPUT={DEFAULT_CONFIG_MAP["SHOW_MCP_OUTPUT"]["value"]}
206
217
  """
@@ -6,7 +6,7 @@ import typer
6
6
  from .chat import FileChatManager
7
7
  from .config import cfg
8
8
  from .const import DEFAULT_CONFIG_INI, DefaultRoleNames, JustifyEnum
9
- from .functions import install_functions, print_functions
9
+ from .functions import install_functions, print_functions, print_mcp
10
10
  from .role import RoleManager
11
11
 
12
12
  app = typer.Typer(
@@ -209,6 +209,29 @@ def main(
209
209
  show_default=False,
210
210
  callback=override_config,
211
211
  ),
212
+ # ------------------- MCP Options -------------------
213
+ enable_mcp: bool = typer.Option( # noqa: F841
214
+ cfg["ENABLE_MCP"],
215
+ "--enable-mcp/--disable-mcp",
216
+ help=f"Enable/disable MCP in API requests [dim](default: {'enabled' if cfg['ENABLE_MCP'] else 'disabled'})[/dim]",
217
+ rich_help_panel="MCP Options",
218
+ callback=override_config,
219
+ ),
220
+ show_mcp_output: bool = typer.Option( # noqa: F841
221
+ cfg["SHOW_MCP_OUTPUT"],
222
+ "--show-mcp-output/--hide-mcp-output",
223
+ help=f"Show the output of MCP [dim](default: {'show' if cfg['SHOW_MCP_OUTPUT'] else 'hide'})[/dim]",
224
+ rich_help_panel="MCP Options",
225
+ show_default=False,
226
+ callback=override_config,
227
+ ),
228
+ list_mcp: bool = typer.Option( # noqa: F841
229
+ False,
230
+ "--list-mcp",
231
+ help="List all available mcp.",
232
+ rich_help_panel="MCP Options",
233
+ callback=print_mcp,
234
+ ),
212
235
  ):
213
236
  """YAICLI: Your AI assistant in the command line.
214
237
 
@@ -1,9 +1,10 @@
1
+ import json
1
2
  import shutil
2
3
  from pathlib import Path
3
4
  from typing import Any
4
5
 
5
6
  from ..console import get_console
6
- from ..const import FUNCTIONS_DIR
7
+ from ..const import FUNCTIONS_DIR, MCP_JSON_PATH
7
8
  from ..utils import option_callback
8
9
 
9
10
  console = get_console()
@@ -37,3 +38,14 @@ def print_functions(cls, _: Any) -> None:
37
38
  if file.name.startswith("_"):
38
39
  continue
39
40
  console.print(file)
41
+
42
+
43
+ @option_callback
44
+ def print_mcp(cls, _: Any) -> None:
45
+ """List all available mcp"""
46
+ if not MCP_JSON_PATH.exists():
47
+ console.print("No mcp config found, please add your mcp config in ~/.config/yaicli/mcp.json")
48
+ return
49
+ with open(MCP_JSON_PATH, "r") as f:
50
+ mcp_config = json.load(f)
51
+ console.print_json(data=mcp_config)
@@ -0,0 +1,153 @@
1
+ from typing import Generator, List, Optional, Union
2
+
3
+ from ..config import cfg
4
+ from ..console import get_console
5
+ from ..schemas import ChatMessage, LLMResponse, RefreshLive, ToolCall
6
+ from ..tools import execute_tool_call
7
+ from ..tools.mcp import MCP_TOOL_NAME_PREFIX
8
+ from .provider import Provider, ProviderFactory
9
+
10
+
11
+ class LLMClient:
12
+ """
13
+ LLM Client that coordinates provider interactions and tool calling
14
+
15
+ This class handles the higher level logic of:
16
+ 1. Getting responses from LLM providers
17
+ 2. Managing tool calls and their execution
18
+ 3. Handling conversation flow with tools
19
+ """
20
+
21
+ def __init__(
22
+ self,
23
+ provider: Optional[Provider] = None,
24
+ provider_name: str = "",
25
+ config: dict = cfg,
26
+ verbose: bool = False,
27
+ **kwargs,
28
+ ):
29
+ """
30
+ Initialize LLM client
31
+
32
+ Args:
33
+ provider: Optional pre-initialized Provider instance
34
+ provider_name: Name of the provider to use if provider not provided
35
+ config: Configuration dictionary
36
+ verbose: Whether to enable verbose logging
37
+ """
38
+ self.config = config
39
+ self.verbose = verbose
40
+ self.console = get_console()
41
+ self.enable_function = self.config["ENABLE_FUNCTIONS"]
42
+ self.enable_mcp = self.config["ENABLE_MCP"]
43
+
44
+ # Use provided provider or create one
45
+ if provider:
46
+ self.provider = provider
47
+ elif provider_name:
48
+ self.provider = ProviderFactory.create_provider(provider_name, config=config, verbose=verbose, **kwargs)
49
+ else:
50
+ provider_name = config.get("PROVIDER", "openai").lower()
51
+ self.provider = ProviderFactory.create_provider(provider_name, config=config, verbose=verbose, **kwargs)
52
+
53
+ self.max_recursion_depth = config.get("MAX_RECURSION_DEPTH", 5)
54
+
55
+ def completion_with_tools(
56
+ self,
57
+ messages: List[ChatMessage],
58
+ stream: bool = False,
59
+ recursion_depth: int = 0,
60
+ ) -> Generator[Union[LLMResponse, RefreshLive], None, None]:
61
+ """
62
+ Get completion from provider with tool calling support
63
+
64
+ Args:
65
+ messages: List of messages for the conversation
66
+ stream: Whether to stream the response
67
+ recursion_depth: Current recursion depth for tool calls
68
+
69
+ Yields:
70
+ LLMResponse objects and control signals
71
+ """
72
+ if recursion_depth >= self.max_recursion_depth:
73
+ self.console.print(
74
+ f"Maximum recursion depth ({self.max_recursion_depth}) reached, stopping further tool calls",
75
+ style="yellow",
76
+ )
77
+ return
78
+
79
+ # Get completion from provider and collect response data
80
+ assistant_response_content = ""
81
+ # Providers may return identical tool calls with the same ID in a single response during streaming
82
+ tool_calls: dict[str, ToolCall] = {}
83
+
84
+ # Stream responses and collect data
85
+ for llm_response in self.provider.completion(messages, stream=stream):
86
+ yield llm_response # Forward response to caller
87
+
88
+ # Collect content and tool calls for potential tool execution
89
+ if llm_response.content:
90
+ assistant_response_content += llm_response.content
91
+ if llm_response.tool_call and llm_response.tool_call.id not in tool_calls:
92
+ tool_calls[llm_response.tool_call.id] = llm_response.tool_call
93
+
94
+ # Check if we need to execute tools
95
+ if not tool_calls or not (self.enable_function or self.enable_mcp):
96
+ return
97
+
98
+ # Filter valid tool calls based on enabled features
99
+ valid_tool_calls = self._get_valid_tool_calls(tool_calls)
100
+ if not valid_tool_calls:
101
+ return
102
+
103
+ # Execute tools and continue conversation
104
+ yield from self._execute_tools_and_continue(
105
+ messages, assistant_response_content, valid_tool_calls, stream, recursion_depth
106
+ )
107
+
108
+ def _get_valid_tool_calls(self, tool_calls: dict[str, ToolCall]) -> List[ToolCall]:
109
+ """Filter tool calls based on enabled features"""
110
+ valid_tool_calls = []
111
+
112
+ for tool_call in tool_calls.values():
113
+ is_mcp = tool_call.name.startswith(MCP_TOOL_NAME_PREFIX)
114
+
115
+ if is_mcp and self.enable_mcp:
116
+ valid_tool_calls.append(tool_call)
117
+ elif not is_mcp and self.enable_function:
118
+ valid_tool_calls.append(tool_call)
119
+
120
+ return valid_tool_calls
121
+
122
+ def _execute_tools_and_continue(
123
+ self,
124
+ messages: List[ChatMessage],
125
+ assistant_response_content: str,
126
+ tool_calls: List[ToolCall],
127
+ stream: bool,
128
+ recursion_depth: int,
129
+ ) -> Generator[Union[LLMResponse, RefreshLive], None, None]:
130
+ """Execute tool calls and continue the conversation"""
131
+ # Signal that new content is coming
132
+ yield RefreshLive()
133
+
134
+ # Add assistant message with tool calls to history (only once)
135
+ messages.append(ChatMessage(role="assistant", content=assistant_response_content, tool_calls=tool_calls))
136
+
137
+ # Execute each tool call and add results to messages
138
+ tool_role = self.provider.detect_tool_role()
139
+
140
+ for tool_call in tool_calls:
141
+ function_result, _ = execute_tool_call(tool_call)
142
+
143
+ messages.append(
144
+ ChatMessage(
145
+ role=tool_role,
146
+ content=function_result,
147
+ name=tool_call.name,
148
+ tool_call_id=tool_call.id,
149
+ )
150
+ )
151
+
152
+ # Continue the conversation with updated history
153
+ yield from self.completion_with_tools(messages, stream=stream, recursion_depth=recursion_depth + 1)
@@ -63,7 +63,7 @@ class AI21Provider(OpenAIProvider):
63
63
  if finish_reason == "tool_calls" and not content:
64
64
  # tool call assistant message, content can't be empty
65
65
  # Error code: 422 - {'detail': {'error': ['Value error, message content must not be an empty string']}}
66
- content = tool_call.id
66
+ content = tool_call.id if tool_call else ""
67
67
 
68
68
  # Generate response object
69
69
  yield LLMResponse(
@@ -72,3 +72,35 @@ class AI21Provider(OpenAIProvider):
72
72
  tool_call=tool_call if finish_reason == "tool_calls" else None,
73
73
  finish_reason=finish_reason,
74
74
  )
75
+
76
+ def _process_tool_call_chunk(self, tool_calls, existing_tool_call=None):
77
+ """Process a tool call chunk from AI21 API response
78
+
79
+ Tool calls from AI21 are delivered across multiple chunks:
80
+ - First chunk contains function name
81
+ - Subsequent chunks contain arguments data
82
+ - Final chunk (with finish_reason='tool_calls') contains complete arguments
83
+
84
+ Args:
85
+ tool_calls: Tool call data from current chunk
86
+ existing_tool_call: Previously accumulated tool call object
87
+
88
+ Returns:
89
+ ToolCall: Updated tool call object with accumulated data
90
+ """
91
+ # Get the first (and only) tool call from the chunk
92
+ call = tool_calls[0]
93
+
94
+ if existing_tool_call is None:
95
+ # First chunk - create new tool call with function name
96
+ return ToolCall(id=call.id, name=call.function.name, arguments="{}")
97
+ else:
98
+ # Update existing tool call with new arguments data
99
+ # Keep existing data and update with new information
100
+ existing_arguments = existing_tool_call.arguments
101
+ new_arguments = call.function.arguments if hasattr(call.function, "arguments") else "{}"
102
+
103
+ # Combine arguments (new arguments should override if available)
104
+ combined_arguments = new_arguments if new_arguments else existing_arguments
105
+
106
+ return ToolCall(id=existing_tool_call.id, name=existing_tool_call.name, arguments=combined_arguments)