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.
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .env
7
+ .venv/
8
+ *.log
9
+ attached_assets/
@@ -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,8 @@
1
+ class Models:
2
+ KIMI = "kimi"
3
+ K1 = "k1"
4
+ K2D6 = "k2d6"
5
+ K2D6_THINKING = "k2d6-thinking"
6
+ K2D6_AGENT = "k2d6-agent"
7
+ K2D6_AGENT_ULTRA = "k2d6-agent-ultra"
8
+ OK_COMPUTER = "ok-computer"
@@ -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"]