sogni-client 5.27.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.
Files changed (39) hide show
  1. sogni_client-5.27.1/.gitignore +19 -0
  2. sogni_client-5.27.1/LICENSE +15 -0
  3. sogni_client-5.27.1/PKG-INFO +284 -0
  4. sogni_client-5.27.1/README.md +253 -0
  5. sogni_client-5.27.1/examples/generate_image.py +27 -0
  6. sogni_client-5.27.1/examples/krea_identity_edit.py +59 -0
  7. sogni_client-5.27.1/examples/stream_chat.py +23 -0
  8. sogni_client-5.27.1/pyproject.toml +74 -0
  9. sogni_client-5.27.1/sogni_client/__init__.py +118 -0
  10. sogni_client-5.27.1/sogni_client/account.py +958 -0
  11. sogni_client-5.27.1/sogni_client/attribution.py +255 -0
  12. sogni_client-5.27.1/sogni_client/auth.py +227 -0
  13. sogni_client-5.27.1/sogni_client/chat.py +1213 -0
  14. sogni_client-5.27.1/sogni_client/client.py +196 -0
  15. sogni_client-5.27.1/sogni_client/data/hosted_tools.json +2049 -0
  16. sogni_client-5.27.1/sogni_client/errors.py +155 -0
  17. sogni_client-5.27.1/sogni_client/events.py +103 -0
  18. sogni_client-5.27.1/sogni_client/projects.py +3306 -0
  19. sogni_client-5.27.1/sogni_client/py.typed +1 -0
  20. sogni_client-5.27.1/sogni_client/recovery.py +214 -0
  21. sogni_client-5.27.1/sogni_client/replay.py +91 -0
  22. sogni_client-5.27.1/sogni_client/stats.py +24 -0
  23. sogni_client-5.27.1/sogni_client/transport.py +565 -0
  24. sogni_client-5.27.1/sogni_client/utils.py +366 -0
  25. sogni_client-5.27.1/sogni_client/workflows.py +504 -0
  26. sogni_client-5.27.1/tests/conftest.py +14 -0
  27. sogni_client-5.27.1/tests/fixtures/hosted-tool-alias-parity.generated.json +296 -0
  28. sogni_client-5.27.1/tests/test_account.py +556 -0
  29. sogni_client-5.27.1/tests/test_attribution.py +79 -0
  30. sogni_client-5.27.1/tests/test_auth.py +268 -0
  31. sogni_client-5.27.1/tests/test_chat.py +863 -0
  32. sogni_client-5.27.1/tests/test_client.py +288 -0
  33. sogni_client-5.27.1/tests/test_errors.py +156 -0
  34. sogni_client-5.27.1/tests/test_events.py +84 -0
  35. sogni_client-5.27.1/tests/test_projects.py +1314 -0
  36. sogni_client-5.27.1/tests/test_recovery.py +336 -0
  37. sogni_client-5.27.1/tests/test_transport.py +397 -0
  38. sogni_client-5.27.1/tests/test_utils.py +231 -0
  39. sogni_client-5.27.1/tests/test_workflows.py +487 -0
@@ -0,0 +1,19 @@
1
+ .venv/
2
+ .pytest_cache/
3
+ .ruff_cache/
4
+ __pycache__/
5
+ *.py[cod]
6
+ *.egg-info/
7
+ .coverage
8
+ htmlcov/
9
+ build/
10
+ dist/
11
+ .env
12
+ .env.*
13
+ !.env.example
14
+ *.key
15
+ *.pem
16
+ *.p12
17
+ *.pfx
18
+ .pypirc
19
+ .DS_Store
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 Sogni AI
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
14
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
+ PERFORMANCE OF THIS SOFTWARE.
@@ -0,0 +1,284 @@
1
+ Metadata-Version: 2.5
2
+ Name: sogni-client
3
+ Version: 5.27.1
4
+ Summary: Async Python SDK for image, video, audio, and LLM inference on the Sogni Supernet
5
+ Project-URL: Homepage, https://www.sogni.ai
6
+ Project-URL: Documentation, https://docs.sogni.ai/sogni-sdk/python/
7
+ Project-URL: Repository, https://github.com/Sogni-AI/sogni-client-python
8
+ Author: Sogni AI
9
+ License-Expression: ISC
10
+ License-File: LICENSE
11
+ Keywords: generative-ai,image-generation,llm,sogni,video-generation
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Framework :: AsyncIO
14
+ Classifier: License :: OSI Approved :: ISC License (ISCL)
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: Programming Language :: Python :: 3.14
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: eth-account<1,>=0.13
23
+ Requires-Dist: httpx<1,>=0.27
24
+ Requires-Dist: websockets<17,>=13
25
+ Provides-Extra: dev
26
+ Requires-Dist: build>=1.2; extra == 'dev'
27
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
28
+ Requires-Dist: pytest>=8.3; extra == 'dev'
29
+ Requires-Dist: ruff>=0.9; extra == 'dev'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # Sogni Client for Python
33
+
34
+ An async Python SDK for image, video, audio, and LLM inference on the Sogni
35
+ Supernet. It follows the public surface and wire protocol of the TypeScript
36
+ `sogni-client`, while using Python naming conventions and async iterators.
37
+
38
+ > The Python port is currently beta. Keep credentials in environment variables
39
+ > or your system keychain; never commit them to source control.
40
+
41
+ [Official quickstart](https://docs.sogni.ai/sogni-sdk/python/) ·
42
+ [Examples](https://github.com/Sogni-AI/sogni-client-python/tree/main/examples) ·
43
+ [Sogni API reference](https://docs.sogni.ai/api-reference/)
44
+
45
+ ## Install
46
+
47
+ Install the latest beta directly from the official GitHub repository:
48
+
49
+ ```bash
50
+ python -m pip install "sogni-client @ git+https://github.com/Sogni-AI/sogni-client-python.git@main"
51
+ ```
52
+
53
+ For an editable source checkout:
54
+
55
+ ```bash
56
+ git clone https://github.com/Sogni-AI/sogni-client-python.git
57
+ cd sogni-client-python
58
+ python -m pip install -e .
59
+ ```
60
+
61
+ Python 3.10 or newer is required.
62
+
63
+ ## Create an image
64
+
65
+ ```python
66
+ import asyncio
67
+ import os
68
+
69
+ from sogni_client import SogniClient
70
+
71
+
72
+ async def main() -> None:
73
+ async with await SogniClient.create(api_key=os.environ["SOGNI_API_KEY"]) as sogni:
74
+ project = await sogni.projects.create(
75
+ type="image",
76
+ model_id="z_image_turbo_bf16",
77
+ positive_prompt="A tiny observatory above a sea of clouds",
78
+ negative_prompt="text, watermark",
79
+ number_of_media=1,
80
+ width=1024,
81
+ height=1024,
82
+ steps=8,
83
+ )
84
+ print(await project.wait_for_completion())
85
+
86
+
87
+ asyncio.run(main())
88
+ ```
89
+
90
+ `SogniClient.create()` generates a unique application ID when one is not
91
+ provided. Pass `app_id="..."` when you deliberately need a stable socket
92
+ identity.
93
+
94
+ ## Edit an image with Krea 2 Identity Edit
95
+
96
+ Pass one or two local reference images through `context_images`. For two-image
97
+ edits, place the base scene first and the identity or detail reference second.
98
+
99
+ ```python
100
+ project = await sogni.projects.create(
101
+ type="image",
102
+ model_id="krea2_identity_edit_v1_2",
103
+ positive_prompt=(
104
+ "Change only the jacket to vivid sapphire blue. Preserve the exact "
105
+ "facial identity, expression, framing, background, and lighting."
106
+ ),
107
+ number_of_media=1,
108
+ width=1024,
109
+ height=1024,
110
+ steps=10,
111
+ guidance=1,
112
+ token_type="spark",
113
+ context_images=["reference.png"],
114
+ )
115
+ print(await project.wait_for_completion(timeout=900))
116
+ ```
117
+
118
+ The runnable example accepts one or two image paths and can also create a batch:
119
+
120
+ ```bash
121
+ python examples/krea_identity_edit.py reference.png \
122
+ --prompt "Change only the jacket to vivid sapphire blue; preserve identity."
123
+
124
+ python examples/krea_identity_edit.py scene.png identity.png \
125
+ --prompt "Use the first image as the base scene and the second for identity." \
126
+ --count 4
127
+ ```
128
+
129
+ ## Chat
130
+
131
+ Socket-backed completion:
132
+
133
+ ```python
134
+ result = await sogni.chat.completions.create(
135
+ model="qwen3.6-35b-a3b-gguf-iq4xs",
136
+ messages=[{"role": "user", "content": "Give me three visual concepts."}],
137
+ )
138
+ print(result["content"])
139
+ ```
140
+
141
+ Hosted OpenAI-compatible completion:
142
+
143
+ ```python
144
+ result = await sogni.chat.hosted.create(
145
+ model="qwen3.6-35b-a3b-gguf-iq4xs",
146
+ messages=[{"role": "user", "content": "Describe a surreal album cover."}],
147
+ )
148
+ ```
149
+
150
+ For streaming socket chat, pass `stream=True` and iterate over the returned
151
+ `ChatStream` with `async for`.
152
+
153
+ ## Durable workflows
154
+
155
+ ```python
156
+ workflow = await sogni.workflows.start(
157
+ input={"prompt": "Create a four-panel character turnaround"},
158
+ idempotency_key="turnaround-001",
159
+ )
160
+
161
+ async for event in sogni.workflows.stream_events(workflow["id"]):
162
+ print(event["event"], event["data"])
163
+ ```
164
+
165
+ The client also exposes:
166
+
167
+ - `sogni.account` for authentication, balances, rewards, transactions, and subscriptions
168
+ - `sogni.projects` for generation, uploads, model discovery, and estimates
169
+ - `sogni.chat` for socket, hosted, tool, and durable-run APIs
170
+ - `sogni.workflows` and `sogni.workflows.templates`
171
+ - `sogni.replay` and `sogni.stats`
172
+
173
+ Python `snake_case` arguments are preferred. Common JavaScript-style aliases
174
+ remain accepted to simplify migration.
175
+
176
+ ## Resuming projects after a reconnect
177
+
178
+ Generation keeps running on the Supernet while your socket is down. A dropped
179
+ connection is a transport gap, not a failure: tracked projects stay alive, the
180
+ client reconnects with capped exponential backoff for as long as the session is
181
+ authenticated, and on every `authenticated` handshake it reconciles with the
182
+ server. Whatever the client missed is replayed through the normal `project` /
183
+ `job` events, so listeners attached before the gap keep receiving updates and
184
+ `wait_for_completion()` still resolves.
185
+
186
+ Projects the server knows about but this client does not (a restart, a second
187
+ client sharing the account, cleared local state) are rebuilt as tracked
188
+ `Project` instances with `project.recovered is True`. Their `params` are
189
+ reconstructed from the original request; asset inputs are not recoverable.
190
+
191
+ ```python
192
+ # Every reconciliation reports what changed. `snapshot` is the raw server view,
193
+ # for apps that keep their own project store.
194
+ sogni.projects.on("projectsSynced", lambda r: print(r["reason"], r["active"], r["lost"]))
195
+
196
+ # In-flight projects this client was not tracking; they are tracked now, so
197
+ # `project` / `job` events follow as usual.
198
+ sogni.projects.on("activeProjectsRecovered", lambda projects: ...)
199
+
200
+ # Projects that finished while this client was away, result URLs already resolved.
201
+ sogni.projects.on("completedProjectsRecovered", lambda projects: ...)
202
+
203
+ # Ask for a fresh reconciliation yourself, e.g. after waking from sleep.
204
+ await sogni.projects.sync()
205
+ ```
206
+
207
+ A project the server no longer lists is looked up on the REST API (which only
208
+ stores finished projects) a few times before it is declared lost; it then fails
209
+ with an error where `is_project_lost_error(error)` is `True`. Apps that persist
210
+ project ids themselves can run the same lookup with
211
+ `sogni.projects.resolve_missing(ids)`.
212
+
213
+ The same snapshot answers "is anything rendering elsewhere on this account?" —
214
+ `sogni.projects.list_projects_elsewhere()` returns those in-flight projects
215
+ read-only (`appSource`, `status`, `model`, per-job step counts). The socket
216
+ rate-limits it to 20 calls per 10s per account, so poll on the order of tens of
217
+ seconds.
218
+
219
+ Recovery is per app instance: the server hands projects back to the `appId` that
220
+ created them, so persist your `appId` and reuse it across restarts.
221
+
222
+ ## Sensitive content
223
+
224
+ `job.is_nsfw` means the server **withheld** the media: the render ran with the
225
+ Sensitive Content Filter on, a signal fired, and there is nothing to download.
226
+ When the artist turns the filter off the media is delivered and merely labelled
227
+ — that case reports `job.nsfw_detected` with `job.nsfw_sources` (`prompt`
228
+ and/or `image`), has a `result_url` like any other result, and leaves
229
+ `job.is_nsfw` false. Use `job.has_result_media` (or `job.is_withheld`) to decide
230
+ whether media exists, and the viewer's own filter setting to decide whether to
231
+ blur it.
232
+
233
+ ## Compatibility
234
+
235
+ This release tracks the current TypeScript source at `5.27.1`. The
236
+ REST, WebSocket, and SSE contracts are covered by credential-free protocol
237
+ tests, including authentication refresh, uploads, project state recovery,
238
+ streaming chat, workflows, templates, replay, and the canonical 25 hosted-tool
239
+ schemas.
240
+
241
+ Current model and transport coverage includes LTX 2.5, MiniMax H3 in all four
242
+ tiers (Standard, 8-step Balanced, 4-step LightX2V Turbo, and the separate
243
+ FastH3 `fastvideo-int8` Turbo engine), Seedance 2.5, Wan 3 and Wan 3.0 Enhanced,
244
+ RTX VSR, MiniMax Music 3, LoRA catalog discovery, queue start estimates,
245
+ live-benchmarked render/total time on cost quotes, in-flight project recovery
246
+ across reconnects, confirmed cancellation, and connection/workload attribution.
247
+
248
+ The Python API is async-first; `AsyncSogniClient` is an alias of
249
+ `SogniClient`, not a synchronous wrapper. Browser-only cookie coordination and
250
+ multi-tab behavior have no Python equivalent. Local image references are
251
+ uploaded with their detected MIME type, but the TypeScript client's optional
252
+ browser-side image resizing is not reproduced. All 25 canonical tool schemas
253
+ are exposed; the local project-backed executor handles the six direct media
254
+ generation tools, while the remaining tools run through the hosted or durable
255
+ chat APIs. Live, credentialed smoke tests are intentionally separate from the
256
+ default test suite.
257
+
258
+ ## Token authentication
259
+
260
+ ```python
261
+ sogni = await SogniClient.create(auth_type="token")
262
+ await sogni.set_tokens(token=access_token, refresh_token=refresh_token)
263
+ ```
264
+
265
+ Username/password login and signing are available through `sogni.account.login`.
266
+ API-key use does not require storing a wallet password.
267
+
268
+ ## Development
269
+
270
+ ```bash
271
+ python -m pip install -e '.[dev]'
272
+ pytest
273
+ ruff check sogni_client tests
274
+ ruff format --check sogni_client tests
275
+ python -m build
276
+ ```
277
+
278
+ Live integration tests require explicit credentials and are not run by default.
279
+
280
+ ## Documentation
281
+
282
+ - [Python SDK quickstart](https://docs.sogni.ai/sogni-sdk/python/)
283
+ - [Sogni SDK overview](https://docs.sogni.ai/sogni-sdk/)
284
+ - [REST API reference](https://docs.sogni.ai/api-reference/)
@@ -0,0 +1,253 @@
1
+ # Sogni Client for Python
2
+
3
+ An async Python SDK for image, video, audio, and LLM inference on the Sogni
4
+ Supernet. It follows the public surface and wire protocol of the TypeScript
5
+ `sogni-client`, while using Python naming conventions and async iterators.
6
+
7
+ > The Python port is currently beta. Keep credentials in environment variables
8
+ > or your system keychain; never commit them to source control.
9
+
10
+ [Official quickstart](https://docs.sogni.ai/sogni-sdk/python/) ·
11
+ [Examples](https://github.com/Sogni-AI/sogni-client-python/tree/main/examples) ·
12
+ [Sogni API reference](https://docs.sogni.ai/api-reference/)
13
+
14
+ ## Install
15
+
16
+ Install the latest beta directly from the official GitHub repository:
17
+
18
+ ```bash
19
+ python -m pip install "sogni-client @ git+https://github.com/Sogni-AI/sogni-client-python.git@main"
20
+ ```
21
+
22
+ For an editable source checkout:
23
+
24
+ ```bash
25
+ git clone https://github.com/Sogni-AI/sogni-client-python.git
26
+ cd sogni-client-python
27
+ python -m pip install -e .
28
+ ```
29
+
30
+ Python 3.10 or newer is required.
31
+
32
+ ## Create an image
33
+
34
+ ```python
35
+ import asyncio
36
+ import os
37
+
38
+ from sogni_client import SogniClient
39
+
40
+
41
+ async def main() -> None:
42
+ async with await SogniClient.create(api_key=os.environ["SOGNI_API_KEY"]) as sogni:
43
+ project = await sogni.projects.create(
44
+ type="image",
45
+ model_id="z_image_turbo_bf16",
46
+ positive_prompt="A tiny observatory above a sea of clouds",
47
+ negative_prompt="text, watermark",
48
+ number_of_media=1,
49
+ width=1024,
50
+ height=1024,
51
+ steps=8,
52
+ )
53
+ print(await project.wait_for_completion())
54
+
55
+
56
+ asyncio.run(main())
57
+ ```
58
+
59
+ `SogniClient.create()` generates a unique application ID when one is not
60
+ provided. Pass `app_id="..."` when you deliberately need a stable socket
61
+ identity.
62
+
63
+ ## Edit an image with Krea 2 Identity Edit
64
+
65
+ Pass one or two local reference images through `context_images`. For two-image
66
+ edits, place the base scene first and the identity or detail reference second.
67
+
68
+ ```python
69
+ project = await sogni.projects.create(
70
+ type="image",
71
+ model_id="krea2_identity_edit_v1_2",
72
+ positive_prompt=(
73
+ "Change only the jacket to vivid sapphire blue. Preserve the exact "
74
+ "facial identity, expression, framing, background, and lighting."
75
+ ),
76
+ number_of_media=1,
77
+ width=1024,
78
+ height=1024,
79
+ steps=10,
80
+ guidance=1,
81
+ token_type="spark",
82
+ context_images=["reference.png"],
83
+ )
84
+ print(await project.wait_for_completion(timeout=900))
85
+ ```
86
+
87
+ The runnable example accepts one or two image paths and can also create a batch:
88
+
89
+ ```bash
90
+ python examples/krea_identity_edit.py reference.png \
91
+ --prompt "Change only the jacket to vivid sapphire blue; preserve identity."
92
+
93
+ python examples/krea_identity_edit.py scene.png identity.png \
94
+ --prompt "Use the first image as the base scene and the second for identity." \
95
+ --count 4
96
+ ```
97
+
98
+ ## Chat
99
+
100
+ Socket-backed completion:
101
+
102
+ ```python
103
+ result = await sogni.chat.completions.create(
104
+ model="qwen3.6-35b-a3b-gguf-iq4xs",
105
+ messages=[{"role": "user", "content": "Give me three visual concepts."}],
106
+ )
107
+ print(result["content"])
108
+ ```
109
+
110
+ Hosted OpenAI-compatible completion:
111
+
112
+ ```python
113
+ result = await sogni.chat.hosted.create(
114
+ model="qwen3.6-35b-a3b-gguf-iq4xs",
115
+ messages=[{"role": "user", "content": "Describe a surreal album cover."}],
116
+ )
117
+ ```
118
+
119
+ For streaming socket chat, pass `stream=True` and iterate over the returned
120
+ `ChatStream` with `async for`.
121
+
122
+ ## Durable workflows
123
+
124
+ ```python
125
+ workflow = await sogni.workflows.start(
126
+ input={"prompt": "Create a four-panel character turnaround"},
127
+ idempotency_key="turnaround-001",
128
+ )
129
+
130
+ async for event in sogni.workflows.stream_events(workflow["id"]):
131
+ print(event["event"], event["data"])
132
+ ```
133
+
134
+ The client also exposes:
135
+
136
+ - `sogni.account` for authentication, balances, rewards, transactions, and subscriptions
137
+ - `sogni.projects` for generation, uploads, model discovery, and estimates
138
+ - `sogni.chat` for socket, hosted, tool, and durable-run APIs
139
+ - `sogni.workflows` and `sogni.workflows.templates`
140
+ - `sogni.replay` and `sogni.stats`
141
+
142
+ Python `snake_case` arguments are preferred. Common JavaScript-style aliases
143
+ remain accepted to simplify migration.
144
+
145
+ ## Resuming projects after a reconnect
146
+
147
+ Generation keeps running on the Supernet while your socket is down. A dropped
148
+ connection is a transport gap, not a failure: tracked projects stay alive, the
149
+ client reconnects with capped exponential backoff for as long as the session is
150
+ authenticated, and on every `authenticated` handshake it reconciles with the
151
+ server. Whatever the client missed is replayed through the normal `project` /
152
+ `job` events, so listeners attached before the gap keep receiving updates and
153
+ `wait_for_completion()` still resolves.
154
+
155
+ Projects the server knows about but this client does not (a restart, a second
156
+ client sharing the account, cleared local state) are rebuilt as tracked
157
+ `Project` instances with `project.recovered is True`. Their `params` are
158
+ reconstructed from the original request; asset inputs are not recoverable.
159
+
160
+ ```python
161
+ # Every reconciliation reports what changed. `snapshot` is the raw server view,
162
+ # for apps that keep their own project store.
163
+ sogni.projects.on("projectsSynced", lambda r: print(r["reason"], r["active"], r["lost"]))
164
+
165
+ # In-flight projects this client was not tracking; they are tracked now, so
166
+ # `project` / `job` events follow as usual.
167
+ sogni.projects.on("activeProjectsRecovered", lambda projects: ...)
168
+
169
+ # Projects that finished while this client was away, result URLs already resolved.
170
+ sogni.projects.on("completedProjectsRecovered", lambda projects: ...)
171
+
172
+ # Ask for a fresh reconciliation yourself, e.g. after waking from sleep.
173
+ await sogni.projects.sync()
174
+ ```
175
+
176
+ A project the server no longer lists is looked up on the REST API (which only
177
+ stores finished projects) a few times before it is declared lost; it then fails
178
+ with an error where `is_project_lost_error(error)` is `True`. Apps that persist
179
+ project ids themselves can run the same lookup with
180
+ `sogni.projects.resolve_missing(ids)`.
181
+
182
+ The same snapshot answers "is anything rendering elsewhere on this account?" —
183
+ `sogni.projects.list_projects_elsewhere()` returns those in-flight projects
184
+ read-only (`appSource`, `status`, `model`, per-job step counts). The socket
185
+ rate-limits it to 20 calls per 10s per account, so poll on the order of tens of
186
+ seconds.
187
+
188
+ Recovery is per app instance: the server hands projects back to the `appId` that
189
+ created them, so persist your `appId` and reuse it across restarts.
190
+
191
+ ## Sensitive content
192
+
193
+ `job.is_nsfw` means the server **withheld** the media: the render ran with the
194
+ Sensitive Content Filter on, a signal fired, and there is nothing to download.
195
+ When the artist turns the filter off the media is delivered and merely labelled
196
+ — that case reports `job.nsfw_detected` with `job.nsfw_sources` (`prompt`
197
+ and/or `image`), has a `result_url` like any other result, and leaves
198
+ `job.is_nsfw` false. Use `job.has_result_media` (or `job.is_withheld`) to decide
199
+ whether media exists, and the viewer's own filter setting to decide whether to
200
+ blur it.
201
+
202
+ ## Compatibility
203
+
204
+ This release tracks the current TypeScript source at `5.27.1`. The
205
+ REST, WebSocket, and SSE contracts are covered by credential-free protocol
206
+ tests, including authentication refresh, uploads, project state recovery,
207
+ streaming chat, workflows, templates, replay, and the canonical 25 hosted-tool
208
+ schemas.
209
+
210
+ Current model and transport coverage includes LTX 2.5, MiniMax H3 in all four
211
+ tiers (Standard, 8-step Balanced, 4-step LightX2V Turbo, and the separate
212
+ FastH3 `fastvideo-int8` Turbo engine), Seedance 2.5, Wan 3 and Wan 3.0 Enhanced,
213
+ RTX VSR, MiniMax Music 3, LoRA catalog discovery, queue start estimates,
214
+ live-benchmarked render/total time on cost quotes, in-flight project recovery
215
+ across reconnects, confirmed cancellation, and connection/workload attribution.
216
+
217
+ The Python API is async-first; `AsyncSogniClient` is an alias of
218
+ `SogniClient`, not a synchronous wrapper. Browser-only cookie coordination and
219
+ multi-tab behavior have no Python equivalent. Local image references are
220
+ uploaded with their detected MIME type, but the TypeScript client's optional
221
+ browser-side image resizing is not reproduced. All 25 canonical tool schemas
222
+ are exposed; the local project-backed executor handles the six direct media
223
+ generation tools, while the remaining tools run through the hosted or durable
224
+ chat APIs. Live, credentialed smoke tests are intentionally separate from the
225
+ default test suite.
226
+
227
+ ## Token authentication
228
+
229
+ ```python
230
+ sogni = await SogniClient.create(auth_type="token")
231
+ await sogni.set_tokens(token=access_token, refresh_token=refresh_token)
232
+ ```
233
+
234
+ Username/password login and signing are available through `sogni.account.login`.
235
+ API-key use does not require storing a wallet password.
236
+
237
+ ## Development
238
+
239
+ ```bash
240
+ python -m pip install -e '.[dev]'
241
+ pytest
242
+ ruff check sogni_client tests
243
+ ruff format --check sogni_client tests
244
+ python -m build
245
+ ```
246
+
247
+ Live integration tests require explicit credentials and are not run by default.
248
+
249
+ ## Documentation
250
+
251
+ - [Python SDK quickstart](https://docs.sogni.ai/sogni-sdk/python/)
252
+ - [Sogni SDK overview](https://docs.sogni.ai/sogni-sdk/)
253
+ - [REST API reference](https://docs.sogni.ai/api-reference/)
@@ -0,0 +1,27 @@
1
+ """Generate one image with an API key from the environment."""
2
+
3
+ import asyncio
4
+ import os
5
+
6
+ from sogni_client import SogniClient
7
+
8
+
9
+ async def main() -> None:
10
+ api_key = os.environ["SOGNI_API_KEY"]
11
+ async with await SogniClient.create(api_key=api_key) as sogni:
12
+ project = await sogni.projects.create(
13
+ type="image",
14
+ model_id="z_image_turbo_bf16",
15
+ positive_prompt="A glass greenhouse drifting above Singapore at dawn",
16
+ negative_prompt="text, watermark",
17
+ number_of_media=1,
18
+ width=1024,
19
+ height=1024,
20
+ steps=8,
21
+ )
22
+ for url in await project.wait_for_completion():
23
+ print(url)
24
+
25
+
26
+ if __name__ == "__main__":
27
+ asyncio.run(main())
@@ -0,0 +1,59 @@
1
+ """Edit one or two local reference images with Krea 2 Identity Edit v1.2."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import asyncio
7
+ import os
8
+ from pathlib import Path
9
+
10
+ from sogni_client import SogniClient
11
+
12
+
13
+ def parse_args() -> argparse.Namespace:
14
+ parser = argparse.ArgumentParser(description=__doc__)
15
+ parser.add_argument(
16
+ "reference_images",
17
+ nargs="+",
18
+ type=Path,
19
+ help="One or two local reference images; place the base scene first.",
20
+ )
21
+ parser.add_argument("--prompt", required=True, help="The edit instruction.")
22
+ parser.add_argument("--count", type=int, default=1, help="Number of outputs (default: 1).")
23
+ parser.add_argument("--width", type=int, default=1024)
24
+ parser.add_argument("--height", type=int, default=1024)
25
+ parser.add_argument("--steps", type=int, default=10)
26
+ args = parser.parse_args()
27
+ if not 1 <= len(args.reference_images) <= 2:
28
+ parser.error("Krea 2 Identity Edit requires one or two reference images")
29
+ if args.count < 1:
30
+ parser.error("--count must be at least 1")
31
+ for reference in args.reference_images:
32
+ if not reference.is_file():
33
+ parser.error(f"reference image does not exist: {reference}")
34
+ return args
35
+
36
+
37
+ async def main(args: argparse.Namespace) -> None:
38
+ async with await SogniClient.create(api_key=os.environ["SOGNI_API_KEY"]) as sogni:
39
+ project = await sogni.projects.create(
40
+ type="image",
41
+ model_id="krea2_identity_edit_v1_2",
42
+ positive_prompt=args.prompt,
43
+ number_of_media=args.count,
44
+ width=args.width,
45
+ height=args.height,
46
+ steps=args.steps,
47
+ guidance=1,
48
+ token_type="spark",
49
+ context_images=args.reference_images,
50
+ )
51
+ # No client-side timeout: a timeout here would abandon a render that is
52
+ # still running on the Supernet. The project carries its own runtime
53
+ # budget, and the SDK resumes it across reconnects.
54
+ for url in await project.wait_for_completion():
55
+ print(url)
56
+
57
+
58
+ if __name__ == "__main__":
59
+ asyncio.run(main(parse_args()))
@@ -0,0 +1,23 @@
1
+ """Stream an LLM response over the Sogni socket transport."""
2
+
3
+ import asyncio
4
+ import os
5
+
6
+ from sogni_client import SogniClient
7
+
8
+
9
+ async def main() -> None:
10
+ api_key = os.environ["SOGNI_API_KEY"]
11
+ async with await SogniClient.create(api_key=api_key) as sogni:
12
+ stream = await sogni.chat.completions.create(
13
+ model="qwen3.6-35b-a3b-gguf-iq4xs",
14
+ messages=[{"role": "user", "content": "Pitch three surreal album covers."}],
15
+ stream=True,
16
+ )
17
+ async for chunk in stream:
18
+ print(chunk.get("content", ""), end="", flush=True)
19
+ print()
20
+
21
+
22
+ if __name__ == "__main__":
23
+ asyncio.run(main())