bxlai 1.0.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.
bxlai-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.4
2
+ Name: bxlai
3
+ Version: 1.0.0
4
+ Summary: BX AI library for Kilo AI models
5
+ Requires-Python: >=3.8
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: requests
8
+
9
+ # bxAI
10
+
11
+ Python library built from the supplied Kilo model catalog.
12
+
13
+ Snapshot:
14
+ - Total models: 400
15
+ - Free models: 18
16
+ - Token models: 382
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ pip install requests
22
+ ```
23
+
24
+ Copy `bxAI.py` and `models.json` into the same folder.
25
+
26
+ ## Usage
27
+
28
+ Free model:
29
+
30
+ ```python
31
+ from bxAI import Ai
32
+
33
+ print(Ai(1, "هلو شلونك؟"))
34
+ ```
35
+
36
+ Paid/token model:
37
+
38
+ ```python
39
+ from bxAI import AIT
40
+
41
+ print(AIT("YOUR_KILO_TOKEN", 25, "اكتبلي برنامج بايثون"))
42
+ ```
43
+
44
+ Show models:
45
+
46
+ ```python
47
+ from bxAI import MODLES
48
+
49
+ MODLES()
50
+ ```
51
+
52
+ The terminal shows only BXCode/BXL aliases and model numbers.
53
+ Original provider/model names are not printed by `MODLES()`.
54
+
55
+ ## Refresh
56
+
57
+ Kilo's live catalog changes. To replace the bundled snapshot:
58
+
59
+ ```python
60
+ from bxAI import refresh_models
61
+
62
+ print(refresh_models())
63
+ ```
64
+
65
+ `refresh_models()` fetches Kilo's `/models` endpoint and regenerates the
66
+ BXCode/BXL aliases.
bxlai-1.0.0/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # bxAI
2
+
3
+ Python library built from the supplied Kilo model catalog.
4
+
5
+ Snapshot:
6
+ - Total models: 400
7
+ - Free models: 18
8
+ - Token models: 382
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ pip install requests
14
+ ```
15
+
16
+ Copy `bxAI.py` and `models.json` into the same folder.
17
+
18
+ ## Usage
19
+
20
+ Free model:
21
+
22
+ ```python
23
+ from bxAI import Ai
24
+
25
+ print(Ai(1, "هلو شلونك؟"))
26
+ ```
27
+
28
+ Paid/token model:
29
+
30
+ ```python
31
+ from bxAI import AIT
32
+
33
+ print(AIT("YOUR_KILO_TOKEN", 25, "اكتبلي برنامج بايثون"))
34
+ ```
35
+
36
+ Show models:
37
+
38
+ ```python
39
+ from bxAI import MODLES
40
+
41
+ MODLES()
42
+ ```
43
+
44
+ The terminal shows only BXCode/BXL aliases and model numbers.
45
+ Original provider/model names are not printed by `MODLES()`.
46
+
47
+ ## Refresh
48
+
49
+ Kilo's live catalog changes. To replace the bundled snapshot:
50
+
51
+ ```python
52
+ from bxAI import refresh_models
53
+
54
+ print(refresh_models())
55
+ ```
56
+
57
+ `refresh_models()` fetches Kilo's `/models` endpoint and regenerates the
58
+ BXCode/BXL aliases.
bxlai-1.0.0/bxAI.py ADDED
@@ -0,0 +1,306 @@
1
+ """
2
+ bxAI - Lightweight Python wrapper for the Kilo AI Gateway.
3
+
4
+ Public API:
5
+ Ai(model_number, question)
6
+ AIT(token, model_number, question)
7
+ MODLES()
8
+
9
+ The display name of every model is intentionally replaced with a BXCode/BXL alias.
10
+ The real Kilo model IDs are kept internally in models.json because the gateway
11
+ needs the actual ID to make a request.
12
+
13
+ The bundled models.json is a snapshot of the Kilo model catalog supplied with
14
+ this package. Kilo's live catalog can change, so use refresh_models() when you
15
+ want to replace it with the current catalog.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ import os
22
+ import shutil
23
+ from typing import Any, Dict, List, Optional
24
+
25
+ import requests
26
+
27
+ BASE_URL = "https://api.kilo.ai/api/gateway"
28
+ CHAT_URL = f"{BASE_URL}/chat/completions"
29
+ MODELS_URL = f"{BASE_URL}/models"
30
+
31
+ _PAGE_SIZE = 18
32
+ _HERE = os.path.dirname(os.path.abspath(__file__))
33
+ _MODELS_FILE = os.path.join(_HERE, "models.json")
34
+
35
+ # Keep the catalog private to the module.
36
+ _MODELS: List[Dict[str, Any]] = []
37
+
38
+
39
+ def _load_models() -> None:
40
+ global _MODELS
41
+ with open(_MODELS_FILE, "r", encoding="utf-8") as f:
42
+ _MODELS = json.load(f)
43
+
44
+
45
+ _load_models()
46
+
47
+
48
+ def _find_model(number: int) -> Dict[str, Any]:
49
+ try:
50
+ number = int(number)
51
+ except (TypeError, ValueError):
52
+ raise ValueError("رقم النموذج يجب أن يكون رقمًا.")
53
+
54
+ for model in _MODELS:
55
+ if model["number"] == number:
56
+ return model
57
+
58
+ raise ValueError(f"ماكو نموذج بالرقم {number}.")
59
+
60
+
61
+ def _request(model: Dict[str, Any], question: str, token: Optional[str] = None) -> str:
62
+ if not isinstance(question, str) or not question.strip():
63
+ raise ValueError("السؤال فارغ.")
64
+
65
+ headers = {
66
+ "Content-Type": "application/json",
67
+ }
68
+
69
+ # Kilo allows unauthenticated requests for free models.
70
+ if token:
71
+ headers["Authorization"] = f"Bearer {token.strip()}"
72
+
73
+ payload = {
74
+ "model": model["id"],
75
+ "messages": [
76
+ {
77
+ "role": "user",
78
+ "content": question,
79
+ }
80
+ ],
81
+ "stream": False,
82
+ }
83
+
84
+ response = requests.post(
85
+ CHAT_URL,
86
+ headers=headers,
87
+ json=payload,
88
+ timeout=90,
89
+ )
90
+
91
+ if response.status_code >= 400:
92
+ try:
93
+ detail = response.json()
94
+ except Exception:
95
+ detail = response.text
96
+
97
+ raise RuntimeError(
98
+ f"Kilo API HTTP {response.status_code}: {detail}"
99
+ )
100
+
101
+ data = response.json()
102
+
103
+ try:
104
+ content = data["choices"][0]["message"]["content"]
105
+ except (KeyError, IndexError, TypeError):
106
+ raise RuntimeError(f"استجابة غير متوقعة من Kilo: {data}")
107
+
108
+ # Some gateways/providers may return structured content.
109
+ if isinstance(content, list):
110
+ parts = []
111
+ for item in content:
112
+ if isinstance(item, dict) and "text" in item:
113
+ parts.append(str(item["text"]))
114
+ elif isinstance(item, str):
115
+ parts.append(item)
116
+ return "".join(parts)
117
+
118
+ return str(content)
119
+
120
+
121
+ def Ai(model_number: int, question: str) -> str:
122
+ """
123
+ Ask a FREE model without a token.
124
+
125
+ Example:
126
+ print(Ai(1, "هلو شلونك؟"))
127
+ """
128
+ model = _find_model(model_number)
129
+
130
+ if not model["free"]:
131
+ raise PermissionError(
132
+ f"{model['alias']} نموذج غير مجاني. استخدم:\n"
133
+ f'AIT("YOUR_KILO_TOKEN", {model["number"]}, "السؤال")'
134
+ )
135
+
136
+ return _request(model, question)
137
+
138
+
139
+ def AIT(token: str, model_number: int, question: str) -> str:
140
+ """
141
+ Ask a model using a Kilo API token.
142
+
143
+ Intended for paid/non-free models.
144
+
145
+ Example:
146
+ print(AIT("YOUR_TOKEN", 25, "اكتبلي كود بايثون"))
147
+ """
148
+ if not isinstance(token, str) or not token.strip():
149
+ raise ValueError("توكن Kilo فارغ.")
150
+
151
+ model = _find_model(model_number)
152
+ return _request(model, question, token=token)
153
+
154
+
155
+ def _print_page(items: List[Dict[str, Any]], title: str, page: int, pages: int) -> None:
156
+ width = shutil.get_terminal_size((80, 24)).columns
157
+ width = max(50, min(width, 110))
158
+
159
+ print("\n" + "=" * width)
160
+ print(f" bxAI | {title} | صفحة {page}/{pages}")
161
+ print("=" * width)
162
+
163
+ for model in items:
164
+ status = "FREE" if model["free"] else "TOKEN"
165
+ print(
166
+ f'{model["number"]:>3} | '
167
+ f'{model["alias"]:<14} | '
168
+ f'{status}'
169
+ )
170
+
171
+ print("-" * width)
172
+
173
+
174
+ def MODLES() -> None:
175
+ """
176
+ Display the complete catalog in terminal pages.
177
+
178
+ Free models are shown first.
179
+ Press Enter for the next page.
180
+ After the free section, the paid/token section is shown.
181
+ Press q to exit.
182
+ """
183
+ if not _MODELS:
184
+ print("ماكو نماذج محملة.")
185
+ return
186
+
187
+ sections = [
188
+ ("FREE MODELS", [m for m in _MODELS if m["free"]]),
189
+ ("TOKEN MODELS", [m for m in _MODELS if not m["free"]]),
190
+ ]
191
+
192
+ for section_title, items in sections:
193
+ if not items:
194
+ continue
195
+
196
+ pages = (len(items) + _PAGE_SIZE - 1) // _PAGE_SIZE
197
+
198
+ for page in range(1, pages + 1):
199
+ start = (page - 1) * _PAGE_SIZE
200
+ chunk = items[start:start + _PAGE_SIZE]
201
+
202
+ _print_page(chunk, section_title, page, pages)
203
+
204
+ if page < pages:
205
+ choice = input("Enter = التالي | q = خروج: ").strip().lower()
206
+ if choice == "q":
207
+ return
208
+ else:
209
+ choice = input("Enter = القسم التالي | q = خروج: ").strip().lower()
210
+ if choice == "q":
211
+ return
212
+
213
+ print("\nانتهت قائمة bxAI.")
214
+
215
+
216
+ def get_model(number: int) -> Dict[str, Any]:
217
+ """Return internal model metadata for advanced users."""
218
+ return dict(_find_model(number))
219
+
220
+
221
+ def count_models() -> Dict[str, int]:
222
+ """Return counts without exposing original model names."""
223
+ return {
224
+ "total": len(_MODELS),
225
+ "free": sum(1 for m in _MODELS if m["free"]),
226
+ "token": sum(1 for m in _MODELS if not m["free"]),
227
+ }
228
+
229
+
230
+ def refresh_models(token: Optional[str] = None) -> Dict[str, int]:
231
+ """
232
+ Replace models.json with Kilo's current /models catalog.
233
+
234
+ GET /models does not require authentication. A token is accepted only
235
+ for compatibility with callers that want to pass one.
236
+
237
+ Display aliases are regenerated as BXCode-### / BXL-### and original
238
+ model names are never used in the MODLES() display.
239
+ """
240
+ headers = {"Content-Type": "application/json"}
241
+ if token:
242
+ headers["Authorization"] = f"Bearer {token.strip()}"
243
+
244
+ response = requests.get(MODELS_URL, headers=headers, timeout=30)
245
+ response.raise_for_status()
246
+
247
+ data = response.json()
248
+ raw = data.get("data")
249
+
250
+ if not isinstance(raw, list):
251
+ raise RuntimeError("Kilo /models لم يرجع قائمة نماذج.")
252
+
253
+ # Prefer Kilo's explicit isFree flag. Also accept zero prompt+completion
254
+ # pricing as free if the flag is absent.
255
+ def is_free(m: Dict[str, Any]) -> bool:
256
+ if m.get("isFree") is True:
257
+ return True
258
+ pricing = m.get("pricing") or {}
259
+ return str(pricing.get("prompt")) == "0" and str(pricing.get("completion")) == "0"
260
+
261
+ free = [m for m in raw if is_free(m)]
262
+ paid = [m for m in raw if not is_free(m)]
263
+
264
+ new_models = []
265
+
266
+ for idx, m in enumerate(free, 1):
267
+ number = idx
268
+ alias = f"BXCode-{number:03d}" if number % 2 else f"BXL-{number:03d}"
269
+ new_models.append({
270
+ "number": number,
271
+ "alias": alias,
272
+ "id": m["id"],
273
+ "free": True,
274
+ "context_length": m.get("context_length")
275
+ or m.get("top_provider", {}).get("context_length"),
276
+ })
277
+
278
+ start = len(free) + 1
279
+ for offset, m in enumerate(paid):
280
+ number = start + offset
281
+ alias = f"BXCode-{number:03d}" if number % 2 else f"BXL-{number:03d}"
282
+ new_models.append({
283
+ "number": number,
284
+ "alias": alias,
285
+ "id": m["id"],
286
+ "free": False,
287
+ "context_length": m.get("context_length")
288
+ or m.get("top_provider", {}).get("context_length"),
289
+ })
290
+
291
+ with open(_MODELS_FILE, "w", encoding="utf-8") as f:
292
+ json.dump(new_models, f, ensure_ascii=False, indent=2)
293
+
294
+ _load_models()
295
+
296
+ return count_models()
297
+
298
+
299
+ __all__ = [
300
+ "Ai",
301
+ "AIT",
302
+ "MODLES",
303
+ "get_model",
304
+ "count_models",
305
+ "refresh_models",
306
+ ]
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.4
2
+ Name: bxlai
3
+ Version: 1.0.0
4
+ Summary: BX AI library for Kilo AI models
5
+ Requires-Python: >=3.8
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: requests
8
+
9
+ # bxAI
10
+
11
+ Python library built from the supplied Kilo model catalog.
12
+
13
+ Snapshot:
14
+ - Total models: 400
15
+ - Free models: 18
16
+ - Token models: 382
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ pip install requests
22
+ ```
23
+
24
+ Copy `bxAI.py` and `models.json` into the same folder.
25
+
26
+ ## Usage
27
+
28
+ Free model:
29
+
30
+ ```python
31
+ from bxAI import Ai
32
+
33
+ print(Ai(1, "هلو شلونك؟"))
34
+ ```
35
+
36
+ Paid/token model:
37
+
38
+ ```python
39
+ from bxAI import AIT
40
+
41
+ print(AIT("YOUR_KILO_TOKEN", 25, "اكتبلي برنامج بايثون"))
42
+ ```
43
+
44
+ Show models:
45
+
46
+ ```python
47
+ from bxAI import MODLES
48
+
49
+ MODLES()
50
+ ```
51
+
52
+ The terminal shows only BXCode/BXL aliases and model numbers.
53
+ Original provider/model names are not printed by `MODLES()`.
54
+
55
+ ## Refresh
56
+
57
+ Kilo's live catalog changes. To replace the bundled snapshot:
58
+
59
+ ```python
60
+ from bxAI import refresh_models
61
+
62
+ print(refresh_models())
63
+ ```
64
+
65
+ `refresh_models()` fetches Kilo's `/models` endpoint and regenerates the
66
+ BXCode/BXL aliases.
@@ -0,0 +1,8 @@
1
+ README.md
2
+ bxAI.py
3
+ pyproject.toml
4
+ bxlai.egg-info/PKG-INFO
5
+ bxlai.egg-info/SOURCES.txt
6
+ bxlai.egg-info/dependency_links.txt
7
+ bxlai.egg-info/requires.txt
8
+ bxlai.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ requests
@@ -0,0 +1 @@
1
+ bxAI
@@ -0,0 +1,14 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "bxlai"
7
+ version = "1.0.0"
8
+ description = "BX AI library for Kilo AI models"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ dependencies = ["requests"]
12
+
13
+ [tool.setuptools]
14
+ py-modules = ["bxAI"]
bxlai-1.0.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+