kimi-nocost 0.1.1__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.
- kimi_nocost-0.1.1/.gitignore +9 -0
- kimi_nocost-0.1.1/LICENSE +21 -0
- kimi_nocost-0.1.1/PKG-INFO +314 -0
- kimi_nocost-0.1.1/README.md +290 -0
- kimi_nocost-0.1.1/kimi_nocost/__init__.py +6 -0
- kimi_nocost-0.1.1/kimi_nocost/client.py +185 -0
- kimi_nocost-0.1.1/kimi_nocost/errors.py +24 -0
- kimi_nocost-0.1.1/kimi_nocost/models.py +8 -0
- kimi_nocost-0.1.1/kimi_nocost/session.py +72 -0
- kimi_nocost-0.1.1/pyproject.toml +36 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 addy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kimi-nocost
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Unofficial Python client for Kimi AI — no API key required, reverse-engineered
|
|
5
|
+
Project-URL: Homepage, https://github.com/pooraddyy/kimi-nocost
|
|
6
|
+
Project-URL: Repository, https://github.com/pooraddyy/kimi-nocost
|
|
7
|
+
Project-URL: Issues, https://github.com/pooraddyy/kimi-nocost/issues
|
|
8
|
+
Author-email: addy <pooraddyy@gmail.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ai,chat,kimi,llm,moonshot,nocost,unofficial
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: requests>=2.32.0
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
<div align="center">
|
|
26
|
+
|
|
27
|
+
<h1>kimi-nocost</h1>
|
|
28
|
+
|
|
29
|
+
<p>Unofficial Python client for <strong>Kimi AI</strong> (kimi.moonshot.cn). No API key needed — just your bearer token.</p>
|
|
30
|
+
|
|
31
|
+
<a href="https://pypi.org/project/kimi-nocost/"><img src="https://img.shields.io/pypi/v/kimi-nocost?color=6c5ce7&label=PyPI&logo=pypi&logoColor=white&style=for-the-badge" alt="PyPI Version"/></a>
|
|
32
|
+
<a href="https://pypi.org/project/kimi-nocost/"><img src="https://img.shields.io/pypi/pyversions/kimi-nocost?color=00b894&logo=python&logoColor=white&style=for-the-badge" alt="Python Versions"/></a>
|
|
33
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-fdcb6e?style=for-the-badge&logo=opensourceinitiative&logoColor=white" alt="License"/></a>
|
|
34
|
+
<a href="https://t.me/pythontodayz"><img src="https://img.shields.io/badge/Telegram-Join%20Channel-2CA5E0?style=for-the-badge&logo=telegram&logoColor=white" alt="Telegram"/></a>
|
|
35
|
+
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Features
|
|
41
|
+
|
|
42
|
+
- No API key required — uses your browser session token
|
|
43
|
+
- Persistent sessions — messages stay in the same conversation automatically
|
|
44
|
+
- Streaming support — yield reply chunks in real time
|
|
45
|
+
- Web search — toggle Kimi's built-in search on any message
|
|
46
|
+
- Multi-turn conversations — full context preserved across calls
|
|
47
|
+
- Image upload — send up to 20 images at once
|
|
48
|
+
- File upload — send documents, PDFs, code files (max 100 MB each, 1,000 files, 10 GB total)
|
|
49
|
+
- Built-in Models — switch models easily with `Models.K2D6`, `Models.K2D6_THINKING`, etc.
|
|
50
|
+
- Lightweight — only depends on `requests`
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Install
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install kimi-nocost
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Quick Start
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from kimi_nocost import KimiClient
|
|
66
|
+
|
|
67
|
+
client = KimiClient(token="your-kimi-bearer-token", model="kimi")
|
|
68
|
+
|
|
69
|
+
reply = client.chat("What is the capital of France?")
|
|
70
|
+
print(reply)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
> `model` is required. Pass it as a plain string — `"kimi"`, `"k2d6"`, `"k2d6-thinking"`, etc.
|
|
74
|
+
> Omitting it raises a `TypeError`.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Models
|
|
79
|
+
|
|
80
|
+
Use the built-in `Models` class to switch between Kimi models:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from kimi_nocost import KimiClient, Models
|
|
84
|
+
|
|
85
|
+
client = KimiClient(token="your-token", model="k2d6") # plain string
|
|
86
|
+
|
|
87
|
+
reply = client.chat("Explain transformers")
|
|
88
|
+
print(reply)
|
|
89
|
+
|
|
90
|
+
client.model = Models.K2D6_THINKING
|
|
91
|
+
reply = client.chat("Solve this step by step: 2x + 5 = 13")
|
|
92
|
+
print(reply)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
| Constant | Model ID | Description |
|
|
96
|
+
|----------|----------|-------------|
|
|
97
|
+
| `Models.KIMI` | `kimi` | Default model |
|
|
98
|
+
| `Models.K1` | `k1` | K1 model |
|
|
99
|
+
| `Models.K2D6` | `k2d6` | K2.6 Instant — fast responses |
|
|
100
|
+
| `Models.K2D6_THINKING` | `k2d6-thinking` | K2.6 Thinking — deep reasoning |
|
|
101
|
+
| `Models.K2D6_AGENT` | `k2d6-agent` | K2.6 Agent — research, slides, docs |
|
|
102
|
+
| `Models.K2D6_AGENT_ULTRA` | `k2d6-agent-ultra` | K2.6 Agent Ultra |
|
|
103
|
+
| `Models.OK_COMPUTER` | `ok-computer` | OK Computer agent |
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Multi-Turn Conversation
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
client = KimiClient(token="your-token")
|
|
111
|
+
|
|
112
|
+
reply1 = client.chat("My name is Alex.")
|
|
113
|
+
reply2 = client.chat("What is my name?")
|
|
114
|
+
print(reply2)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Streaming
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from kimi_nocost import KimiClient
|
|
123
|
+
|
|
124
|
+
client = KimiClient(token="your-token")
|
|
125
|
+
|
|
126
|
+
for chunk in client.stream("Explain quantum computing in simple terms"):
|
|
127
|
+
print(chunk, end="", flush=True)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## With Web Search
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
reply = client.chat("Latest news on AI today", use_search=True)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Image Upload
|
|
141
|
+
|
|
142
|
+
Send up to 20 images in a single call:
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
from kimi_nocost import KimiClient
|
|
146
|
+
|
|
147
|
+
client = KimiClient(token="your-token")
|
|
148
|
+
|
|
149
|
+
file_ids = client.upload_images([
|
|
150
|
+
"photo1.jpg",
|
|
151
|
+
"photo2.png",
|
|
152
|
+
(open("screenshot.png", "rb"), "screenshot.png"),
|
|
153
|
+
])
|
|
154
|
+
|
|
155
|
+
reply = client.chat("Describe these images", refs=file_ids)
|
|
156
|
+
print(reply)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## File Upload
|
|
162
|
+
|
|
163
|
+
Send documents, PDFs, or code files (max 100 MB each, 1,000 files per session, 10 GB total):
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
file_id = client.upload_file("report.pdf")
|
|
167
|
+
|
|
168
|
+
reply = client.chat("Summarize this document", refs=[file_id])
|
|
169
|
+
print(reply)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
You can also pass raw bytes with a filename:
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
import io
|
|
176
|
+
|
|
177
|
+
content = b"def hello(): print('world')"
|
|
178
|
+
file_id = client.upload_file(io.BytesIO(content), "hello.py")
|
|
179
|
+
|
|
180
|
+
reply = client.chat("What does this code do?", refs=[file_id])
|
|
181
|
+
print(reply)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Starting a New Chat
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
client.new_chat(name="fresh-start")
|
|
190
|
+
reply = client.chat("Let's start over.")
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Manual Chat ID
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
chat_id = client.new_chat(name="my-session")
|
|
199
|
+
|
|
200
|
+
reply1 = client.chat("My name is Alex.", chat_id=chat_id)
|
|
201
|
+
reply2 = client.chat("What is my name?", chat_id=chat_id)
|
|
202
|
+
print(reply2)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## API Reference
|
|
208
|
+
|
|
209
|
+
### `KimiClient(token, model=Models.KIMI, timeout=60)`
|
|
210
|
+
|
|
211
|
+
| Parameter | Type | Description |
|
|
212
|
+
|-----------|------|-------------|
|
|
213
|
+
| `token` | `str` | Bearer JWT from kimi.moonshot.cn |
|
|
214
|
+
| `model` | `str` | Model ID — use `Models.*` constants |
|
|
215
|
+
| `timeout` | `int` | HTTP timeout in seconds |
|
|
216
|
+
|
|
217
|
+
### Methods
|
|
218
|
+
|
|
219
|
+
| Method | Returns | Description |
|
|
220
|
+
|--------|---------|-------------|
|
|
221
|
+
| `chat(message, chat_id, history, use_search, refs)` | `str` | Send a message, get full reply |
|
|
222
|
+
| `stream(message, chat_id, history, use_search, refs)` | `Generator[str]` | Send a message, yield reply chunks |
|
|
223
|
+
| `new_chat(name)` | `str` | Create a new conversation and set it as active |
|
|
224
|
+
| `upload_file(file, filename)` | `str` | Upload a file, returns file ID |
|
|
225
|
+
| `upload_images(files)` | `List[str]` | Upload up to 20 images, returns list of file IDs |
|
|
226
|
+
| `upload_single(file, filename)` | `str` | Upload a single file or image, returns file ID |
|
|
227
|
+
|
|
228
|
+
### Attributes
|
|
229
|
+
|
|
230
|
+
| Attribute | Type | Description |
|
|
231
|
+
|-----------|------|-------------|
|
|
232
|
+
| `model` | `str` | Current model — change anytime |
|
|
233
|
+
| `chat_id` | `str` | Active conversation ID |
|
|
234
|
+
| `file_count` | `int` | Number of files uploaded this session |
|
|
235
|
+
| `total_size` | `int` | Total bytes uploaded this session |
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## REST API Server
|
|
240
|
+
|
|
241
|
+
The included Flask server exposes HTTP endpoints:
|
|
242
|
+
|
|
243
|
+
| Endpoint | Method | Description |
|
|
244
|
+
|----------|--------|-------------|
|
|
245
|
+
| `POST /chat/new` | POST | Create a new session → returns `chat_id` |
|
|
246
|
+
| `POST /chat` | POST | Send a message → returns `reply` + `chat_id` |
|
|
247
|
+
| `POST /chat/stream` | POST | Stream a reply as plain text |
|
|
248
|
+
| `POST /upload/images` | POST | Upload up to 20 images → returns `file_ids` |
|
|
249
|
+
| `POST /upload/file` | POST | Upload a single file → returns `file_id` |
|
|
250
|
+
| `GET /health` | GET | Health check |
|
|
251
|
+
|
|
252
|
+
Pass your token via `Authorization: Bearer <token>` header.
|
|
253
|
+
|
|
254
|
+
**Chat with files:**
|
|
255
|
+
```json
|
|
256
|
+
{
|
|
257
|
+
"message": "Summarize this",
|
|
258
|
+
"file_ids": ["id1", "id2"],
|
|
259
|
+
"model": "k2d6-thinking"
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
**Upload images** (multipart, field name `images`):
|
|
264
|
+
```bash
|
|
265
|
+
curl -H "Authorization: Bearer <token>" \
|
|
266
|
+
-F "images=@photo1.jpg" \
|
|
267
|
+
-F "images=@photo2.png" \
|
|
268
|
+
http://localhost:8080/upload/images
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**Upload file** (multipart, field name `file`):
|
|
272
|
+
```bash
|
|
273
|
+
curl -H "Authorization: Bearer <token>" \
|
|
274
|
+
-F "file=@report.pdf" \
|
|
275
|
+
http://localhost:8080/upload/file
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## Getting Your Token
|
|
281
|
+
|
|
282
|
+
1. Open [kimi.moonshot.cn](https://kimi.moonshot.cn) in your browser
|
|
283
|
+
2. Open **DevTools** → **Network** → any request
|
|
284
|
+
3. Copy the value of `Authorization: Bearer <token>`
|
|
285
|
+
|
|
286
|
+
Or from iOS/Android using a MITM proxy (e.g. mitmproxy, Charles).
|
|
287
|
+
|
|
288
|
+
The token is a JWT that lasts ~30 days.
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## Demo
|
|
293
|
+
|
|
294
|
+
See [DEMO.md](DEMO.md) for a full working Telegram bot with image & file upload support.
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## Community
|
|
299
|
+
|
|
300
|
+
<div align="center">
|
|
301
|
+
|
|
302
|
+
<a href="https://t.me/pythontodayz">
|
|
303
|
+
<img src="https://img.shields.io/badge/Telegram-%40pythontodayz-2CA5E0?style=for-the-badge&logo=telegram&logoColor=white" alt="Telegram Channel"/>
|
|
304
|
+
</a>
|
|
305
|
+
|
|
306
|
+
Join for Python tutorials, projects, and updates → [t.me/pythontodayz](https://t.me/pythontodayz)
|
|
307
|
+
|
|
308
|
+
</div>
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## License
|
|
313
|
+
|
|
314
|
+
MIT — made by [addy](https://t.me/pythontodayz)
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<h1>kimi-nocost</h1>
|
|
4
|
+
|
|
5
|
+
<p>Unofficial Python client for <strong>Kimi AI</strong> (kimi.moonshot.cn). No API key needed — just your bearer token.</p>
|
|
6
|
+
|
|
7
|
+
<a href="https://pypi.org/project/kimi-nocost/"><img src="https://img.shields.io/pypi/v/kimi-nocost?color=6c5ce7&label=PyPI&logo=pypi&logoColor=white&style=for-the-badge" alt="PyPI Version"/></a>
|
|
8
|
+
<a href="https://pypi.org/project/kimi-nocost/"><img src="https://img.shields.io/pypi/pyversions/kimi-nocost?color=00b894&logo=python&logoColor=white&style=for-the-badge" alt="Python Versions"/></a>
|
|
9
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-fdcb6e?style=for-the-badge&logo=opensourceinitiative&logoColor=white" alt="License"/></a>
|
|
10
|
+
<a href="https://t.me/pythontodayz"><img src="https://img.shields.io/badge/Telegram-Join%20Channel-2CA5E0?style=for-the-badge&logo=telegram&logoColor=white" alt="Telegram"/></a>
|
|
11
|
+
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- No API key required — uses your browser session token
|
|
19
|
+
- Persistent sessions — messages stay in the same conversation automatically
|
|
20
|
+
- Streaming support — yield reply chunks in real time
|
|
21
|
+
- Web search — toggle Kimi's built-in search on any message
|
|
22
|
+
- Multi-turn conversations — full context preserved across calls
|
|
23
|
+
- Image upload — send up to 20 images at once
|
|
24
|
+
- File upload — send documents, PDFs, code files (max 100 MB each, 1,000 files, 10 GB total)
|
|
25
|
+
- Built-in Models — switch models easily with `Models.K2D6`, `Models.K2D6_THINKING`, etc.
|
|
26
|
+
- Lightweight — only depends on `requests`
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install kimi-nocost
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from kimi_nocost import KimiClient
|
|
42
|
+
|
|
43
|
+
client = KimiClient(token="your-kimi-bearer-token", model="kimi")
|
|
44
|
+
|
|
45
|
+
reply = client.chat("What is the capital of France?")
|
|
46
|
+
print(reply)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
> `model` is required. Pass it as a plain string — `"kimi"`, `"k2d6"`, `"k2d6-thinking"`, etc.
|
|
50
|
+
> Omitting it raises a `TypeError`.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Models
|
|
55
|
+
|
|
56
|
+
Use the built-in `Models` class to switch between Kimi models:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from kimi_nocost import KimiClient, Models
|
|
60
|
+
|
|
61
|
+
client = KimiClient(token="your-token", model="k2d6") # plain string
|
|
62
|
+
|
|
63
|
+
reply = client.chat("Explain transformers")
|
|
64
|
+
print(reply)
|
|
65
|
+
|
|
66
|
+
client.model = Models.K2D6_THINKING
|
|
67
|
+
reply = client.chat("Solve this step by step: 2x + 5 = 13")
|
|
68
|
+
print(reply)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
| Constant | Model ID | Description |
|
|
72
|
+
|----------|----------|-------------|
|
|
73
|
+
| `Models.KIMI` | `kimi` | Default model |
|
|
74
|
+
| `Models.K1` | `k1` | K1 model |
|
|
75
|
+
| `Models.K2D6` | `k2d6` | K2.6 Instant — fast responses |
|
|
76
|
+
| `Models.K2D6_THINKING` | `k2d6-thinking` | K2.6 Thinking — deep reasoning |
|
|
77
|
+
| `Models.K2D6_AGENT` | `k2d6-agent` | K2.6 Agent — research, slides, docs |
|
|
78
|
+
| `Models.K2D6_AGENT_ULTRA` | `k2d6-agent-ultra` | K2.6 Agent Ultra |
|
|
79
|
+
| `Models.OK_COMPUTER` | `ok-computer` | OK Computer agent |
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Multi-Turn Conversation
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
client = KimiClient(token="your-token")
|
|
87
|
+
|
|
88
|
+
reply1 = client.chat("My name is Alex.")
|
|
89
|
+
reply2 = client.chat("What is my name?")
|
|
90
|
+
print(reply2)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Streaming
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from kimi_nocost import KimiClient
|
|
99
|
+
|
|
100
|
+
client = KimiClient(token="your-token")
|
|
101
|
+
|
|
102
|
+
for chunk in client.stream("Explain quantum computing in simple terms"):
|
|
103
|
+
print(chunk, end="", flush=True)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## With Web Search
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
reply = client.chat("Latest news on AI today", use_search=True)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Image Upload
|
|
117
|
+
|
|
118
|
+
Send up to 20 images in a single call:
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from kimi_nocost import KimiClient
|
|
122
|
+
|
|
123
|
+
client = KimiClient(token="your-token")
|
|
124
|
+
|
|
125
|
+
file_ids = client.upload_images([
|
|
126
|
+
"photo1.jpg",
|
|
127
|
+
"photo2.png",
|
|
128
|
+
(open("screenshot.png", "rb"), "screenshot.png"),
|
|
129
|
+
])
|
|
130
|
+
|
|
131
|
+
reply = client.chat("Describe these images", refs=file_ids)
|
|
132
|
+
print(reply)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## File Upload
|
|
138
|
+
|
|
139
|
+
Send documents, PDFs, or code files (max 100 MB each, 1,000 files per session, 10 GB total):
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
file_id = client.upload_file("report.pdf")
|
|
143
|
+
|
|
144
|
+
reply = client.chat("Summarize this document", refs=[file_id])
|
|
145
|
+
print(reply)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
You can also pass raw bytes with a filename:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
import io
|
|
152
|
+
|
|
153
|
+
content = b"def hello(): print('world')"
|
|
154
|
+
file_id = client.upload_file(io.BytesIO(content), "hello.py")
|
|
155
|
+
|
|
156
|
+
reply = client.chat("What does this code do?", refs=[file_id])
|
|
157
|
+
print(reply)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Starting a New Chat
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
client.new_chat(name="fresh-start")
|
|
166
|
+
reply = client.chat("Let's start over.")
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Manual Chat ID
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
chat_id = client.new_chat(name="my-session")
|
|
175
|
+
|
|
176
|
+
reply1 = client.chat("My name is Alex.", chat_id=chat_id)
|
|
177
|
+
reply2 = client.chat("What is my name?", chat_id=chat_id)
|
|
178
|
+
print(reply2)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## API Reference
|
|
184
|
+
|
|
185
|
+
### `KimiClient(token, model=Models.KIMI, timeout=60)`
|
|
186
|
+
|
|
187
|
+
| Parameter | Type | Description |
|
|
188
|
+
|-----------|------|-------------|
|
|
189
|
+
| `token` | `str` | Bearer JWT from kimi.moonshot.cn |
|
|
190
|
+
| `model` | `str` | Model ID — use `Models.*` constants |
|
|
191
|
+
| `timeout` | `int` | HTTP timeout in seconds |
|
|
192
|
+
|
|
193
|
+
### Methods
|
|
194
|
+
|
|
195
|
+
| Method | Returns | Description |
|
|
196
|
+
|--------|---------|-------------|
|
|
197
|
+
| `chat(message, chat_id, history, use_search, refs)` | `str` | Send a message, get full reply |
|
|
198
|
+
| `stream(message, chat_id, history, use_search, refs)` | `Generator[str]` | Send a message, yield reply chunks |
|
|
199
|
+
| `new_chat(name)` | `str` | Create a new conversation and set it as active |
|
|
200
|
+
| `upload_file(file, filename)` | `str` | Upload a file, returns file ID |
|
|
201
|
+
| `upload_images(files)` | `List[str]` | Upload up to 20 images, returns list of file IDs |
|
|
202
|
+
| `upload_single(file, filename)` | `str` | Upload a single file or image, returns file ID |
|
|
203
|
+
|
|
204
|
+
### Attributes
|
|
205
|
+
|
|
206
|
+
| Attribute | Type | Description |
|
|
207
|
+
|-----------|------|-------------|
|
|
208
|
+
| `model` | `str` | Current model — change anytime |
|
|
209
|
+
| `chat_id` | `str` | Active conversation ID |
|
|
210
|
+
| `file_count` | `int` | Number of files uploaded this session |
|
|
211
|
+
| `total_size` | `int` | Total bytes uploaded this session |
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## REST API Server
|
|
216
|
+
|
|
217
|
+
The included Flask server exposes HTTP endpoints:
|
|
218
|
+
|
|
219
|
+
| Endpoint | Method | Description |
|
|
220
|
+
|----------|--------|-------------|
|
|
221
|
+
| `POST /chat/new` | POST | Create a new session → returns `chat_id` |
|
|
222
|
+
| `POST /chat` | POST | Send a message → returns `reply` + `chat_id` |
|
|
223
|
+
| `POST /chat/stream` | POST | Stream a reply as plain text |
|
|
224
|
+
| `POST /upload/images` | POST | Upload up to 20 images → returns `file_ids` |
|
|
225
|
+
| `POST /upload/file` | POST | Upload a single file → returns `file_id` |
|
|
226
|
+
| `GET /health` | GET | Health check |
|
|
227
|
+
|
|
228
|
+
Pass your token via `Authorization: Bearer <token>` header.
|
|
229
|
+
|
|
230
|
+
**Chat with files:**
|
|
231
|
+
```json
|
|
232
|
+
{
|
|
233
|
+
"message": "Summarize this",
|
|
234
|
+
"file_ids": ["id1", "id2"],
|
|
235
|
+
"model": "k2d6-thinking"
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Upload images** (multipart, field name `images`):
|
|
240
|
+
```bash
|
|
241
|
+
curl -H "Authorization: Bearer <token>" \
|
|
242
|
+
-F "images=@photo1.jpg" \
|
|
243
|
+
-F "images=@photo2.png" \
|
|
244
|
+
http://localhost:8080/upload/images
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Upload file** (multipart, field name `file`):
|
|
248
|
+
```bash
|
|
249
|
+
curl -H "Authorization: Bearer <token>" \
|
|
250
|
+
-F "file=@report.pdf" \
|
|
251
|
+
http://localhost:8080/upload/file
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Getting Your Token
|
|
257
|
+
|
|
258
|
+
1. Open [kimi.moonshot.cn](https://kimi.moonshot.cn) in your browser
|
|
259
|
+
2. Open **DevTools** → **Network** → any request
|
|
260
|
+
3. Copy the value of `Authorization: Bearer <token>`
|
|
261
|
+
|
|
262
|
+
Or from iOS/Android using a MITM proxy (e.g. mitmproxy, Charles).
|
|
263
|
+
|
|
264
|
+
The token is a JWT that lasts ~30 days.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Demo
|
|
269
|
+
|
|
270
|
+
See [DEMO.md](DEMO.md) for a full working Telegram bot with image & file upload support.
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## Community
|
|
275
|
+
|
|
276
|
+
<div align="center">
|
|
277
|
+
|
|
278
|
+
<a href="https://t.me/pythontodayz">
|
|
279
|
+
<img src="https://img.shields.io/badge/Telegram-%40pythontodayz-2CA5E0?style=for-the-badge&logo=telegram&logoColor=white" alt="Telegram Channel"/>
|
|
280
|
+
</a>
|
|
281
|
+
|
|
282
|
+
Join for Python tutorials, projects, and updates → [t.me/pythontodayz](https://t.me/pythontodayz)
|
|
283
|
+
|
|
284
|
+
</div>
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## License
|
|
289
|
+
|
|
290
|
+
MIT — made by [addy](https://t.me/pythontodayz)
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
from .client import KimiClient
|
|
2
|
+
from .errors import KimiAPIError, KimiAuthError, KimiError, KimiSessionLimitError, KimiUploadError
|
|
3
|
+
from .models import Models
|
|
4
|
+
|
|
5
|
+
__all__ = ["KimiClient", "KimiError", "KimiAuthError", "KimiAPIError", "KimiUploadError", "KimiSessionLimitError", "Models"]
|
|
6
|
+
__version__ = "0.1.1"
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
from typing import Generator, List, Union
|
|
7
|
+
|
|
8
|
+
import requests
|
|
9
|
+
|
|
10
|
+
from .errors import KimiAPIError, KimiAuthError, KimiSessionLimitError, KimiUploadError
|
|
11
|
+
from .models import Models
|
|
12
|
+
from .session import build_headers, create_chat, new_id, stream_completion, upload_file
|
|
13
|
+
|
|
14
|
+
MAX_FILE_SIZE = 100 * 1024 * 1024
|
|
15
|
+
MAX_FILES = 1000
|
|
16
|
+
MAX_TOTAL_SIZE = 10 * 1024 * 1024 * 1024
|
|
17
|
+
MAX_IMAGES_PER_CALL = 20
|
|
18
|
+
|
|
19
|
+
FileInput = Union[str, os.PathLike, bytes, io.IOBase]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class KimiClient:
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
token: str,
|
|
26
|
+
model: str,
|
|
27
|
+
timeout: int = 60,
|
|
28
|
+
) -> None:
|
|
29
|
+
if not token:
|
|
30
|
+
raise KimiAuthError("token is required")
|
|
31
|
+
if not model:
|
|
32
|
+
raise KimiAuthError("model is required — e.g. model='kimi' or model='k2d6'")
|
|
33
|
+
self.token = token
|
|
34
|
+
self.model = model
|
|
35
|
+
self.timeout = timeout
|
|
36
|
+
self.device_id = new_id()
|
|
37
|
+
self.session_id = new_id()
|
|
38
|
+
self.chat_id: str = ""
|
|
39
|
+
self.file_count: int = 0
|
|
40
|
+
self.total_size: int = 0
|
|
41
|
+
self.http = requests.Session()
|
|
42
|
+
self.http.headers.update(build_headers(token, self.device_id, self.session_id))
|
|
43
|
+
|
|
44
|
+
def new_chat(self, name: str = "") -> str:
|
|
45
|
+
self.chat_id = create_chat(self.http, name, self.timeout)
|
|
46
|
+
return self.chat_id
|
|
47
|
+
|
|
48
|
+
def upload_single(self, file: FileInput, filename: str | None = None) -> str:
|
|
49
|
+
if isinstance(file, (str, os.PathLike)):
|
|
50
|
+
path = str(file)
|
|
51
|
+
filename = filename or os.path.basename(path)
|
|
52
|
+
size = os.path.getsize(path)
|
|
53
|
+
if size > MAX_FILE_SIZE:
|
|
54
|
+
raise KimiUploadError(f"{filename} exceeds 100 MB limit")
|
|
55
|
+
if self.file_count >= MAX_FILES:
|
|
56
|
+
raise KimiUploadError("max 1,000 files per user exceeded")
|
|
57
|
+
if self.total_size + size > MAX_TOTAL_SIZE:
|
|
58
|
+
raise KimiUploadError("total upload limit of 10 GB exceeded")
|
|
59
|
+
with open(path, "rb") as fh:
|
|
60
|
+
file_id = upload_file(self.http, fh, filename, self.timeout)
|
|
61
|
+
self.file_count += 1
|
|
62
|
+
self.total_size += size
|
|
63
|
+
return file_id
|
|
64
|
+
|
|
65
|
+
if isinstance(file, bytes):
|
|
66
|
+
data = file
|
|
67
|
+
file_obj = io.BytesIO(data)
|
|
68
|
+
elif isinstance(file, io.IOBase):
|
|
69
|
+
data = file.read()
|
|
70
|
+
file_obj = io.BytesIO(data)
|
|
71
|
+
else:
|
|
72
|
+
raise KimiUploadError("file must be a path, bytes, or file-like object")
|
|
73
|
+
|
|
74
|
+
if not filename:
|
|
75
|
+
raise KimiUploadError("filename is required for bytes/stream uploads")
|
|
76
|
+
size = len(data)
|
|
77
|
+
if size > MAX_FILE_SIZE:
|
|
78
|
+
raise KimiUploadError(f"{filename} exceeds 100 MB limit")
|
|
79
|
+
if self.file_count >= MAX_FILES:
|
|
80
|
+
raise KimiUploadError("max 1,000 files per user exceeded")
|
|
81
|
+
if self.total_size + size > MAX_TOTAL_SIZE:
|
|
82
|
+
raise KimiUploadError("total upload limit of 10 GB exceeded")
|
|
83
|
+
file_id = upload_file(self.http, file_obj, filename, self.timeout)
|
|
84
|
+
self.file_count += 1
|
|
85
|
+
self.total_size += size
|
|
86
|
+
return file_id
|
|
87
|
+
|
|
88
|
+
def upload_images(
|
|
89
|
+
self,
|
|
90
|
+
files: List[Union[FileInput, tuple]],
|
|
91
|
+
) -> List[str]:
|
|
92
|
+
if len(files) > MAX_IMAGES_PER_CALL:
|
|
93
|
+
raise KimiUploadError(f"max {MAX_IMAGES_PER_CALL} images per call")
|
|
94
|
+
ids = []
|
|
95
|
+
for item in files:
|
|
96
|
+
if isinstance(item, tuple):
|
|
97
|
+
file_id = self.upload_single(item[0], item[1] if len(item) > 1 else None)
|
|
98
|
+
else:
|
|
99
|
+
file_id = self.upload_single(item)
|
|
100
|
+
ids.append(file_id)
|
|
101
|
+
return ids
|
|
102
|
+
|
|
103
|
+
def upload_file(
|
|
104
|
+
self,
|
|
105
|
+
file: FileInput,
|
|
106
|
+
filename: str | None = None,
|
|
107
|
+
) -> str:
|
|
108
|
+
return self.upload_single(file, filename)
|
|
109
|
+
|
|
110
|
+
def chat(
|
|
111
|
+
self,
|
|
112
|
+
message: str,
|
|
113
|
+
chat_id: str = "",
|
|
114
|
+
history: list | None = None,
|
|
115
|
+
use_search: bool = False,
|
|
116
|
+
refs: list | None = None,
|
|
117
|
+
) -> str:
|
|
118
|
+
return "".join(
|
|
119
|
+
self.stream(
|
|
120
|
+
message,
|
|
121
|
+
chat_id=chat_id,
|
|
122
|
+
history=history,
|
|
123
|
+
use_search=use_search,
|
|
124
|
+
refs=refs,
|
|
125
|
+
)
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def stream(
|
|
129
|
+
self,
|
|
130
|
+
message: str,
|
|
131
|
+
chat_id: str = "",
|
|
132
|
+
history: list | None = None,
|
|
133
|
+
use_search: bool = False,
|
|
134
|
+
refs: list | None = None,
|
|
135
|
+
) -> Generator[str, None, None]:
|
|
136
|
+
active_chat_id = chat_id or self.chat_id
|
|
137
|
+
if not active_chat_id:
|
|
138
|
+
active_chat_id = self.new_chat()
|
|
139
|
+
else:
|
|
140
|
+
self.chat_id = active_chat_id
|
|
141
|
+
|
|
142
|
+
messages = list(history or []) + [{"role": "user", "content": message}]
|
|
143
|
+
|
|
144
|
+
resp = stream_completion(
|
|
145
|
+
self.http,
|
|
146
|
+
active_chat_id,
|
|
147
|
+
messages,
|
|
148
|
+
self.model,
|
|
149
|
+
use_search,
|
|
150
|
+
self.timeout,
|
|
151
|
+
refs_file=refs or [],
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
if not resp.ok:
|
|
155
|
+
raise KimiAPIError(str(resp.status_code), resp.text[:300])
|
|
156
|
+
|
|
157
|
+
for raw in resp.iter_lines():
|
|
158
|
+
if not raw:
|
|
159
|
+
continue
|
|
160
|
+
line = raw.decode("utf-8") if isinstance(raw, bytes) else raw
|
|
161
|
+
if not line.startswith("data:"):
|
|
162
|
+
continue
|
|
163
|
+
data = line[5:].strip()
|
|
164
|
+
if data == "[DONE]":
|
|
165
|
+
break
|
|
166
|
+
try:
|
|
167
|
+
obj = json.loads(data)
|
|
168
|
+
except (json.JSONDecodeError, ValueError):
|
|
169
|
+
continue
|
|
170
|
+
event = obj.get("event", "")
|
|
171
|
+
if event == "cmpl" and "text" in obj:
|
|
172
|
+
yield obj["text"]
|
|
173
|
+
elif event == "all_done":
|
|
174
|
+
break
|
|
175
|
+
elif "error_type" in obj or "error" in obj:
|
|
176
|
+
code = obj.get("error_type") or str(obj.get("error", "unknown"))
|
|
177
|
+
detail = obj.get("message", "")
|
|
178
|
+
limit_keywords = (
|
|
179
|
+
"context_length", "token_limit", "too_long",
|
|
180
|
+
"context_limit", "length_exceed", "conversation_too_long",
|
|
181
|
+
"chat_context", "max_tokens",
|
|
182
|
+
)
|
|
183
|
+
if any(kw in code.lower() or kw in detail.lower() for kw in limit_keywords):
|
|
184
|
+
raise KimiSessionLimitError(code)
|
|
185
|
+
raise KimiAPIError(code, detail)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
class KimiError(Exception):
|
|
2
|
+
pass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class KimiAuthError(KimiError):
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class KimiAPIError(KimiError):
|
|
10
|
+
def __init__(self, code, detail=""):
|
|
11
|
+
self.code = code
|
|
12
|
+
self.detail = detail
|
|
13
|
+
msg = f"Kimi API error: {code}"
|
|
14
|
+
if detail:
|
|
15
|
+
msg += f" — {detail}"
|
|
16
|
+
super().__init__(msg)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class KimiUploadError(KimiError):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class KimiSessionLimitError(KimiError):
|
|
24
|
+
pass
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import mimetypes
|
|
2
|
+
import uuid
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
BASE = "https://kimi.moonshot.cn"
|
|
6
|
+
FILE_BASE = "https://www.kimi.com/apiv2-files"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def new_id():
|
|
10
|
+
return uuid.uuid4().hex
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def build_headers(token, device_id, session_id):
|
|
14
|
+
return {
|
|
15
|
+
"User-Agent": (
|
|
16
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
|
|
17
|
+
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
|
18
|
+
"Chrome/135.0.0.0 Safari/537.36"
|
|
19
|
+
),
|
|
20
|
+
"Authorization": f"Bearer {token}",
|
|
21
|
+
"Referer": "https://kimi.moonshot.cn/",
|
|
22
|
+
"Origin": "https://kimi.moonshot.cn",
|
|
23
|
+
"x-msh-device-id": device_id,
|
|
24
|
+
"x-msh-session-id": session_id,
|
|
25
|
+
"x-language": "en-US",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def create_chat(http, name, timeout):
|
|
30
|
+
name = name or f"chat-{uuid.uuid4().hex[:8]}"
|
|
31
|
+
r = http.post(
|
|
32
|
+
f"{BASE}/api/chat",
|
|
33
|
+
json={"name": name, "is_example": False},
|
|
34
|
+
timeout=timeout,
|
|
35
|
+
)
|
|
36
|
+
r.raise_for_status()
|
|
37
|
+
return r.json()["id"]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def upload_file(http, file_obj, filename, timeout):
|
|
41
|
+
content_type = mimetypes.guess_type(filename)[0] or "application/octet-stream"
|
|
42
|
+
extra_headers = {
|
|
43
|
+
"Upload-Draft-Interop-Version": "6",
|
|
44
|
+
"Upload-Complete": "?1",
|
|
45
|
+
}
|
|
46
|
+
r = http.post(
|
|
47
|
+
f"{FILE_BASE}/file/upload",
|
|
48
|
+
files={"file": (filename, file_obj, content_type)},
|
|
49
|
+
headers=extra_headers,
|
|
50
|
+
timeout=timeout,
|
|
51
|
+
)
|
|
52
|
+
r.raise_for_status()
|
|
53
|
+
return r.json()["file"]["id"]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def stream_completion(http, chat_id, messages, model, use_search, timeout, refs_file=None):
|
|
57
|
+
body = {
|
|
58
|
+
"messages": messages,
|
|
59
|
+
"use_search": use_search,
|
|
60
|
+
"extend": {"sidebar": True},
|
|
61
|
+
"kimiplus_id": model,
|
|
62
|
+
"use_research": False,
|
|
63
|
+
"refs": [],
|
|
64
|
+
"refs_file": refs_file or [],
|
|
65
|
+
}
|
|
66
|
+
return http.post(
|
|
67
|
+
f"{BASE}/api/chat/{chat_id}/completion/stream",
|
|
68
|
+
json=body,
|
|
69
|
+
stream=True,
|
|
70
|
+
headers={"Content-Type": "application/json", "Accept": "text/event-stream"},
|
|
71
|
+
timeout=timeout,
|
|
72
|
+
)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "kimi-nocost"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "Unofficial Python client for Kimi AI — no API key required, reverse-engineered"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "addy", email = "pooraddyy@gmail.com" }]
|
|
13
|
+
keywords = ["kimi", "moonshot", "ai", "chat", "llm", "unofficial", "nocost"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
24
|
+
]
|
|
25
|
+
dependencies = ["requests>=2.32.0"]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://github.com/pooraddyy/kimi-nocost"
|
|
29
|
+
Repository = "https://github.com/pooraddyy/kimi-nocost"
|
|
30
|
+
Issues = "https://github.com/pooraddyy/kimi-nocost/issues"
|
|
31
|
+
|
|
32
|
+
[tool.hatch.build.targets.wheel]
|
|
33
|
+
packages = ["kimi_nocost"]
|
|
34
|
+
|
|
35
|
+
[tool.hatch.build.targets.sdist]
|
|
36
|
+
include = ["kimi_nocost/", "README.md", "pyproject.toml", "LICENSE"]
|