yaicli 0.5.6__py3-none-any.whl → 0.5.8__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.
pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "yaicli"
3
- version = "0.5.6"
3
+ version = "0.5.8"
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"
yaicli/chat.py CHANGED
@@ -10,8 +10,8 @@ from rich.table import Table
10
10
 
11
11
  from .config import cfg
12
12
  from .console import YaiConsole, get_console
13
- from .schemas import ChatMessage
14
13
  from .exceptions import ChatDeleteError, ChatLoadError, ChatSaveError
14
+ from .schemas import ChatMessage
15
15
  from .utils import option_callback
16
16
 
17
17
  console: YaiConsole = get_console()
@@ -68,7 +68,9 @@ class Chat:
68
68
  data = json.load(f)
69
69
  self.title = data.get("title", self.title)
70
70
  self.date = data.get("date", self.date)
71
- self.history = [ChatMessage(role=msg["role"], content=msg["content"]) for msg in data.get("history", [])]
71
+ self.history = [
72
+ ChatMessage(role=msg["role"], content=msg["content"]) for msg in data.get("history", [])
73
+ ]
72
74
  return True
73
75
  except (json.JSONDecodeError, OSError) as e:
74
76
  raise ChatLoadError(f"Error loading chat: {e}") from e
yaicli/cli.py CHANGED
@@ -17,7 +17,7 @@ from rich.panel import Panel
17
17
  from rich.prompt import Prompt
18
18
 
19
19
  from .chat import Chat, FileChatManager, chat_mgr
20
- from .client import LitellmClient, ChatMessage
20
+ from .client import ChatMessage, LitellmClient
21
21
  from .config import cfg
22
22
  from .console import get_console
23
23
  from .const import (
yaicli/client.py CHANGED
@@ -11,7 +11,7 @@ from rich.panel import Panel
11
11
 
12
12
  from .config import cfg
13
13
  from .console import get_console
14
- from .schemas import LLMResponse, ChatMessage, ToolCall
14
+ from .schemas import ChatMessage, LLMResponse, ToolCall
15
15
  from .tools import (
16
16
  Function,
17
17
  FunctionName,
@@ -46,6 +46,8 @@ class LitellmClient:
46
46
  def __post_init__(self) -> None:
47
47
  """Initialize OpenAI client"""
48
48
  self.pre_tool_call_id = None
49
+ if cfg["PROVIDER"] == "openrouter":
50
+ cfg["EXTRA_HEADERS"].update({"X-Title": "Yaicli", "HTTP-Referer": "https://github.com/belingud/yaicli"})
49
51
 
50
52
  def _convert_messages(self, messages: List[ChatMessage]) -> List[Dict[str, Any]]:
51
53
  """Convert message format to OpenAI API required format"""
@@ -135,6 +137,10 @@ class LitellmClient:
135
137
  }
136
138
 
137
139
  # Add optional parameters
140
+ if cfg["EXTRA_HEADERS"]:
141
+ params["extra_headers"] = cfg["EXTRA_HEADERS"]
142
+ if cfg["EXTRA_BODY"]:
143
+ params["extra_body"] = cfg["EXTRA_BODY"]
138
144
  if cfg["ENABLE_FUNCTIONS"]:
139
145
  params["tools"] = self._convert_functions(list_functions())
140
146
  params["tool_choice"] = "auto"
yaicli/config.py CHANGED
@@ -1,8 +1,9 @@
1
1
  import configparser
2
+ import json
2
3
  from dataclasses import dataclass
3
4
  from functools import lru_cache
4
5
  from os import getenv
5
- from typing import Any, Optional
6
+ from typing import Optional
6
7
 
7
8
  from rich import get_console
8
9
  from rich.console import Console
@@ -70,17 +71,12 @@ class Config(dict):
70
71
  self._load_from_env()
71
72
  self._apply_type_conversion()
72
73
 
73
- def _load_defaults(self) -> dict[str, Any]:
74
- """Load default configuration values as strings.
75
-
76
- Returns:
77
- Dictionary with default configuration values
78
- """
74
+ def _load_defaults(self) -> None:
75
+ """Load default configuration values as strings."""
79
76
  defaults = {k: v["value"] for k, v in DEFAULT_CONFIG_MAP.items()}
80
77
  self.update(defaults)
81
- return defaults
82
78
 
83
- def _ensure_version_updated_config_keys(self):
79
+ def _ensure_version_updated_config_keys(self) -> None:
84
80
  """Ensure configuration keys added in version updates exist in the config file.
85
81
  Appends missing keys to the config file if they don't exist.
86
82
  """
@@ -133,7 +129,7 @@ class Config(dict):
133
129
  Updates the configuration dictionary in-place with properly typed values.
134
130
  Falls back to default values if conversion fails.
135
131
  """
136
- default_values_str = {k: v["value"] for k, v in DEFAULT_CONFIG_MAP.items()}
132
+ default_values_map = {k: v["value"] for k, v in DEFAULT_CONFIG_MAP.items()}
137
133
 
138
134
  for key, config_info in DEFAULT_CONFIG_MAP.items():
139
135
  target_type = config_info["type"]
@@ -142,25 +138,29 @@ class Config(dict):
142
138
 
143
139
  try:
144
140
  if raw_value is None:
145
- raw_value = default_values_str.get(key, "")
141
+ raw_value = default_values_map.get(key, "")
146
142
  if target_type is bool:
147
143
  converted_value = str2bool(raw_value)
148
144
  elif target_type in (int, float, str):
149
145
  converted_value = target_type(raw_value)
150
- except (ValueError, TypeError) as e:
146
+ elif target_type is dict and raw_value:
147
+ converted_value = json.loads(raw_value)
148
+ except (ValueError, TypeError, json.JSONDecodeError) as e:
151
149
  self.console.print(
152
150
  f"[yellow]Warning:[/] Invalid value '{raw_value}' for '{key}'. "
153
- f"Expected type '{target_type.__name__}'. Using default value '{default_values_str[key]}'. Error: {e}",
151
+ f"Expected type '{target_type.__name__}'. Using default value '{default_values_map[key]}'. Error: {e}",
154
152
  style="dim",
155
153
  justify=self["JUSTIFY"],
156
154
  )
157
155
  # Fallback to default string value if conversion fails
158
156
  try:
159
157
  if target_type is bool:
160
- converted_value = str2bool(default_values_str[key])
161
- else:
162
- converted_value = target_type(default_values_str[key])
163
- except (ValueError, TypeError):
158
+ converted_value = str2bool(default_values_map[key])
159
+ elif target_type in (int, float, str):
160
+ converted_value = target_type(default_values_map[key])
161
+ elif target_type is dict:
162
+ converted_value = json.loads(default_values_map[key])
163
+ except (ValueError, TypeError, json.JSONDecodeError):
164
164
  # If default also fails (unlikely), keep the raw merged value or a sensible default
165
165
  self.console.print(
166
166
  f"[red]Error:[/red] Could not convert default value for '{key}'. Using raw value.",
yaicli/const.py CHANGED
@@ -1,4 +1,11 @@
1
- from enum import StrEnum
1
+ try:
2
+ from enum import StrEnum
3
+ except ImportError:
4
+ from enum import Enum
5
+
6
+ class StrEnum(str, Enum):
7
+ """Compatible with python below 3.11"""
8
+
2
9
  from pathlib import Path
3
10
  from tempfile import gettempdir
4
11
  from typing import Any, Literal, Optional
@@ -51,6 +58,8 @@ DEFAULT_MAX_HISTORY: int = 500
51
58
  DEFAULT_AUTO_SUGGEST: BOOL_STR = "true"
52
59
  DEFAULT_SHOW_REASONING: BOOL_STR = "true"
53
60
  DEFAULT_TIMEOUT: int = 60
61
+ DEFAULT_EXTRA_HEADERS: str = "{}"
62
+ DEFAULT_EXTRA_BODY: str = "{}"
54
63
  DEFAULT_INTERACTIVE_ROUND: int = 25
55
64
  DEFAULT_CHAT_HISTORY_DIR: Path = Path(gettempdir()) / "yaicli/chats"
56
65
  DEFAULT_MAX_SAVED_CHATS = 20
@@ -61,21 +70,6 @@ DEFAULT_SHOW_FUNCTION_OUTPUT: BOOL_STR = "true"
61
70
  DEFAULT_REASONING_EFFORT: Optional[Literal["low", "high", "medium"]] = None
62
71
 
63
72
 
64
- class EventTypeEnum(StrEnum):
65
- """Enumeration of possible event types from the SSE stream."""
66
-
67
- ERROR = "error"
68
- REASONING = "reasoning"
69
- REASONING_END = "reasoning_end"
70
- CONTENT = "content"
71
- FINISH = "finish"
72
- TOOL_CALL_START = "tool_call_start"
73
- TOOL_CALL_DELTA = "tool_call_delta"
74
- TOOL_CALL_END = "tool_call_end"
75
- TOOL_RESULT = "tool_result"
76
- TOOL_CALLS_FINISH = "tool_calls_finish"
77
-
78
-
79
73
  SHELL_PROMPT = """You are YAICLI, a shell command generator.
80
74
  The context conversation may contain other types of messages,
81
75
  but you should only respond with a single valid {_shell} shell command for {_os}.
@@ -130,6 +124,8 @@ DEFAULT_CONFIG_MAP = {
130
124
  "TOP_P": {"value": DEFAULT_TOP_P, "env_key": "YAI_TOP_P", "type": float},
131
125
  "MAX_TOKENS": {"value": DEFAULT_MAX_TOKENS, "env_key": "YAI_MAX_TOKENS", "type": int},
132
126
  "TIMEOUT": {"value": DEFAULT_TIMEOUT, "env_key": "YAI_TIMEOUT", "type": int},
127
+ "EXTRA_HEADERS": {"value": DEFAULT_EXTRA_HEADERS, "env_key": "YAI_EXTRA_HEADERS", "type": dict},
128
+ "EXTRA_BODY": {"value": DEFAULT_EXTRA_BODY, "env_key": "YAI_EXTRA_BODY", "type": dict},
133
129
  "REASONING_EFFORT": {"value": DEFAULT_REASONING_EFFORT, "env_key": "YAI_REASONING_EFFORT", "type": str},
134
130
  "INTERACTIVE_ROUND": {
135
131
  "value": DEFAULT_INTERACTIVE_ROUND,
@@ -174,6 +170,10 @@ TEMPERATURE={DEFAULT_CONFIG_MAP["TEMPERATURE"]["value"]}
174
170
  TOP_P={DEFAULT_CONFIG_MAP["TOP_P"]["value"]}
175
171
  MAX_TOKENS={DEFAULT_CONFIG_MAP["MAX_TOKENS"]["value"]}
176
172
  TIMEOUT={DEFAULT_CONFIG_MAP["TIMEOUT"]["value"]}
173
+ # json string
174
+ EXTRA_HEADERS=
175
+ # json string
176
+ EXTRA_BODY=
177
177
  REASONING_EFFORT=
178
178
 
179
179
  # Interactive mode parameters
yaicli/printer.py CHANGED
@@ -8,8 +8,8 @@ from rich.live import Live
8
8
  from .client import RefreshLive
9
9
  from .config import Config, get_config
10
10
  from .console import YaiConsole, get_console
11
- from .schemas import ChatMessage
12
11
  from .render import Markdown, plain_formatter
12
+ from .schemas import ChatMessage
13
13
 
14
14
  if TYPE_CHECKING:
15
15
  from .schemas import LLMResponse
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yaicli
3
- Version: 0.5.6
3
+ Version: 0.5.8
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
@@ -392,6 +392,9 @@ SHOW_FUNCTION_OUTPUT=true
392
392
  | `OS_NAME` | Operating system | `auto` | `YAI_OS_NAME` |
393
393
  | `STREAM` | Enable streaming | `true` | `YAI_STREAM` |
394
394
  | `TIMEOUT` | API timeout (seconds) | `60` | `YAI_TIMEOUT` |
395
+ | `EXTRA_HEADERS` | Extra headers | - | `YAI_EXTRA_HEADERS` |
396
+ | `EXTRA_BODY` | Extra body | - | `YAI_EXTRA_BODY` |
397
+ | `REASONING_EFFORT` | Reasoning effort | - | `YAI_REASONING_EFFORT` |
395
398
  | `INTERACTIVE_ROUND` | Interactive mode rounds | `25` | `YAI_INTERACTIVE_ROUND` |
396
399
  | `CODE_THEME` | Syntax highlighting theme | `monokai` | `YAI_CODE_THEME` |
397
400
  | `TEMPERATURE` | Response randomness | `0.7` | `YAI_TEMPERATURE` |
@@ -449,6 +452,28 @@ Browse available themes at: https://pygments.org/styles/
449
452
 
450
453
  ![monokia theme example](artwork/monokia.png)
451
454
 
455
+ ### Extra Headers and Body
456
+
457
+ You can add extra headers and body to the API request by setting `EXTRA_HEADERS` and `EXTRA_BODY` in the config file.
458
+ The value should be valid json string.
459
+
460
+ ```ini
461
+ EXTRA_HEADERS={"X-Extra-Header": "value"}
462
+ EXTRA_BODY={"extra_key": "extra_value"}
463
+ ```
464
+
465
+ Example: If you want to disable Qwen3's thinking behavior, you can add the following to the config file.
466
+
467
+ ```ini
468
+ EXTRA_BODY={"enable_thinking": false}
469
+ ```
470
+
471
+ Or just limit thinking tokens:
472
+
473
+ ```ini
474
+ EXTRA_BODY={"thinking_budget": 4096}
475
+ ```
476
+
452
477
  ## 🚀 Usage
453
478
 
454
479
  ### Quick Start
@@ -904,6 +929,8 @@ YAICLI is designed with a modular architecture that separates concerns and makes
904
929
  | [Typer](https://typer.tiangolo.com/) | Command-line interface with type hints |
905
930
  | [Rich](https://rich.readthedocs.io/) | Terminal formatting and beautiful display |
906
931
  | [prompt_toolkit](https://python-prompt-toolkit.readthedocs.io/) | Interactive input with history and auto-completion |
932
+ | [litellm](https://litellm.ai/) | LLM provider compatibility |
933
+ | [json-repair](https://github.com/mangiucugna/json_repair) | Repair llm function call arguments |
907
934
 
908
935
  ## 👨‍💻 Contributing
909
936
 
@@ -1,15 +1,15 @@
1
- pyproject.toml,sha256=6p1uC3kyuCkJEw7qEwIFWQ2wgiAV9fqpDmGr6u9hsAs,1963
1
+ pyproject.toml,sha256=uIesu0u5n1nVFJ4MJ4ZO4G9hr4EGlwcysyKybIG3PyM,1963
2
2
  yaicli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- yaicli/chat.py,sha256=DeTmOeBPU-oiOAIaDj2h-auJor0GyVVhrViLYF6zGIM,13638
4
- yaicli/cli.py,sha256=1Flt0FgrKzbabjJpZJCcvnUjXaaLwyD2paRyMixEQV0,22985
5
- yaicli/client.py,sha256=fKUDmn9s7tF9Q2wIB8WhbsjFYIpV0E29t_Vw0qVmVbI,16229
6
- yaicli/config.py,sha256=_mp8P6zXyrdp4TzBfHraOCkjv5DMZMOwiEhQnFYWwZA,6321
3
+ yaicli/chat.py,sha256=_emvZEdgMBth2nQGaNWPf0P45oW2k3bpuIwqsxFcM5A,13676
4
+ yaicli/cli.py,sha256=u4rq04CdLiL-IBzQ0IimPyFAh9XZg9YUTuvM4CxE3pE,22985
5
+ yaicli/client.py,sha256=kdEI4HP1zZQHUf-7kNbE9ItpyRN8zcAGyKeBSb1R4Ro,16565
6
+ yaicli/config.py,sha256=HrWYcelLXE61XX719eVcuuo3292xxf1BNQznWdvjQFQ,6535
7
7
  yaicli/console.py,sha256=vARPJd-3lafutsQWrGntQVjLrYqaJD3qisN82pmuhjU,1973
8
- yaicli/const.py,sha256=FYW8cNqFzZwnYbgr_HXZSzSS8OIU_UsFIn4SZ0zOJ8U,8129
8
+ yaicli/const.py,sha256=mt_6m2jo5-tHEmk3DxKCEc5ek9DcyQGHFAn_HEeMP3k,8155
9
9
  yaicli/entry.py,sha256=gKzN8Yar3tpBd2Z2a80gD3k0W4Sf3lL7jdyws-2y-H0,8687
10
10
  yaicli/exceptions.py,sha256=WBYg8OTJJzaj7lt6HE7ZyBoe5T6A3yZRNCRfWd4iN0c,372
11
11
  yaicli/history.py,sha256=s-57X9FMsaQHF7XySq1gGH_jpd_cHHTYafYu2ECuG6M,2472
12
- yaicli/printer.py,sha256=a409R_4-ppNnen31Pt7KvaaNAFVCzBARYC0T0_EtMbU,8369
12
+ yaicli/printer.py,sha256=J-QPrb0q4Zrx18vwBGIBDQk7RzT4wXJPwdJ9oXLoEVg,8369
13
13
  yaicli/render.py,sha256=k8o2P8fI44PJlyQbs7gmMiu2x2prwajdWn5JIt15BIA,505
14
14
  yaicli/role.py,sha256=PfwiVJIlzg7EzlvMM-kIy6vBK0d5d_J4M1I_fIZGnWk,7399
15
15
  yaicli/schemas.py,sha256=PiuSY7ORZaA4OL_tYm0inwqirHp5M-F3zcCipLwsH9E,571
@@ -17,8 +17,8 @@ yaicli/tools.py,sha256=d-5LXbEB-1Uq5VKSgwlAiNDVOGrHkku2DpmZoorq1zw,3098
17
17
  yaicli/utils.py,sha256=bpo3Xhozpxsaci3FtEIKZ32l4ZdyWMsrHjYGX0tB4J4,4541
18
18
  yaicli/functions/__init__.py,sha256=_FJooQ9GkijG8xLwuU0cr5GBrGnC9Nc6bnCeUjrsT0k,1271
19
19
  yaicli/functions/buildin/execute_shell_command.py,sha256=unl1-F8p6QZajeHdA0u5UpURMJM0WhdWMUWCCCHVRcI,1320
20
- yaicli-0.5.6.dist-info/METADATA,sha256=Vxp8bZrO89wYwWhtXB00o_2XVyF-6UyprAQd8bfVh1g,49194
21
- yaicli-0.5.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
22
- yaicli-0.5.6.dist-info/entry_points.txt,sha256=iYVyQP0PJIm9tQnlQheqT435kK_xdGoi5j9aswGV9hA,66
23
- yaicli-0.5.6.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
24
- yaicli-0.5.6.dist-info/RECORD,,
20
+ yaicli-0.5.8.dist-info/METADATA,sha256=BRZEKpxpZ6kPABv88FGl4ipt8glgJdAn-9ZxiDLAAeo,50349
21
+ yaicli-0.5.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
22
+ yaicli-0.5.8.dist-info/entry_points.txt,sha256=iYVyQP0PJIm9tQnlQheqT435kK_xdGoi5j9aswGV9hA,66
23
+ yaicli-0.5.8.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
24
+ yaicli-0.5.8.dist-info/RECORD,,
File without changes