everypixel-cli 0.2.1__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {everypixel_cli-0.2.1 → everypixel_cli-0.3.0}/CHANGELOG.md +9 -0
- {everypixel_cli-0.2.1 → everypixel_cli-0.3.0}/PKG-INFO +45 -5
- {everypixel_cli-0.2.1 → everypixel_cli-0.3.0}/README.md +44 -4
- everypixel_cli-0.3.0/src/everypixel_cli/__init__.py +1 -0
- everypixel_cli-0.3.0/src/everypixel_cli/application/chat.py +192 -0
- {everypixel_cli-0.2.1 → everypixel_cli-0.3.0}/src/everypixel_cli/application/models.py +1 -1
- {everypixel_cli-0.2.1 → everypixel_cli-0.3.0}/src/everypixel_cli/application/services.py +69 -12
- {everypixel_cli-0.2.1 → everypixel_cli-0.3.0}/src/everypixel_cli/cli.py +106 -4
- {everypixel_cli-0.2.1 → everypixel_cli-0.3.0}/src/everypixel_cli/client.py +89 -17
- {everypixel_cli-0.2.1 → everypixel_cli-0.3.0}/src/everypixel_cli/mcp_server.py +34 -1
- {everypixel_cli-0.2.1 → everypixel_cli-0.3.0}/src/everypixel_cli/output.py +55 -0
- {everypixel_cli-0.2.1 → everypixel_cli-0.3.0}/src/everypixel_cli/resources/openapi.json +567 -9
- {everypixel_cli-0.2.1 → everypixel_cli-0.3.0}/src/everypixel_cli/schemas.py +147 -18
- everypixel_cli-0.2.1/src/everypixel_cli/__init__.py +0 -1
- {everypixel_cli-0.2.1 → everypixel_cli-0.3.0}/.gitignore +0 -0
- {everypixel_cli-0.2.1 → everypixel_cli-0.3.0}/LICENSE +0 -0
- {everypixel_cli-0.2.1 → everypixel_cli-0.3.0}/pyproject.toml +0 -0
- {everypixel_cli-0.2.1 → everypixel_cli-0.3.0}/src/everypixel_cli/__main__.py +0 -0
- {everypixel_cli-0.2.1 → everypixel_cli-0.3.0}/src/everypixel_cli/application/__init__.py +0 -0
- {everypixel_cli-0.2.1 → everypixel_cli-0.3.0}/src/everypixel_cli/application/serialization.py +0 -0
- {everypixel_cli-0.2.1 → everypixel_cli-0.3.0}/src/everypixel_cli/config.py +0 -0
- {everypixel_cli-0.2.1 → everypixel_cli-0.3.0}/src/everypixel_cli/errors.py +0 -0
- {everypixel_cli-0.2.1 → everypixel_cli-0.3.0}/src/everypixel_cli/files.py +0 -0
- {everypixel_cli-0.2.1 → everypixel_cli-0.3.0}/src/everypixel_cli/openapi.py +0 -0
- {everypixel_cli-0.2.1 → everypixel_cli-0.3.0}/src/everypixel_cli/resources/__init__.py +0 -0
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.0] - 2026-09-18
|
|
4
|
+
|
|
5
|
+
- Add GPT Image 2.5 Sunburst and quality selection for GPT Image models.
|
|
6
|
+
- Add GLM-5.3 chat with streaming and tool-call support.
|
|
7
|
+
|
|
8
|
+
## [0.2.2] - 2026-09-11
|
|
9
|
+
|
|
10
|
+
- Increase the default task wait timeout to 30 minutes for long video generation.
|
|
11
|
+
|
|
3
12
|
## [0.2.1] - 2026-09-11
|
|
4
13
|
|
|
5
14
|
- Support up to 9 image, 3 video, and 3 audio references for MiniMax H3/Turbo.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: everypixel-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Official command line interface for Everypixel API
|
|
5
5
|
Project-URL: Homepage, https://labs.everypixel.com
|
|
6
6
|
Project-URL: Repository, https://github.com/everypixel/everypixel-cli
|
|
@@ -35,8 +35,8 @@ Description-Content-Type: text/markdown
|
|
|
35
35
|
# Everypixel CLI
|
|
36
36
|
|
|
37
37
|
Official command-line interface for the [Everypixel API](https://labs.everypixel.com/).
|
|
38
|
-
Generate and edit images and videos,
|
|
39
|
-
automate Everypixel workflows from a terminal or an MCP client.
|
|
38
|
+
Generate and edit images and videos, chat with GLM-5.3, work with audio, run
|
|
39
|
+
visual analysis, and automate Everypixel workflows from a terminal or an MCP client.
|
|
40
40
|
|
|
41
41
|
The CLI is designed for both people and agents: commands use readable terminal
|
|
42
42
|
output by default and can return stable JSON for scripts and tool integrations.
|
|
@@ -181,6 +181,15 @@ to select their aspect ratio. Raster models also accept `--resolution`.
|
|
|
181
181
|
|
|
182
182
|
`--lora-url` is supported only by `zimage`.
|
|
183
183
|
|
|
184
|
+
GPT Image models are `gpt-image-2` and `gpt-image-2.5-sunburst`. Both generation
|
|
185
|
+
and editing accept `--quality low|medium|high` (default: `medium`); Sunburst also
|
|
186
|
+
supports `xhigh` and `max`. Use `--size square` with `--resolution 3k`.
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
everypixel image generate "a detailed botanical illustration" \
|
|
190
|
+
--model gpt-image-2.5-sunburst --quality high --download ./outputs
|
|
191
|
+
```
|
|
192
|
+
|
|
184
193
|
Grok image models are `grok-imagine`, `grok-imagine-2`, and
|
|
185
194
|
`grok-imagine-2-low`. The `grok` alias remains supported. `grok_quality` and
|
|
186
195
|
`wan22` have been removed from the API.
|
|
@@ -281,6 +290,37 @@ everypixel audio tts-create \
|
|
|
281
290
|
--download ./outputs
|
|
282
291
|
```
|
|
283
292
|
|
|
293
|
+
### Chat
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
everypixel chat "Explain how a rainbow forms."
|
|
297
|
+
everypixel chat "Review this idea." --system "Be concise." --stream
|
|
298
|
+
everypixel chat "Solve this problem." --reasoning-effort high --show-reasoning
|
|
299
|
+
everypixel chat --input-file ./conversation.json --output-json
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Chat uses the same credentials as other commands and requires the client's
|
|
303
|
+
`chat` scope. The default model is `glm-5.3`. Each invocation sends one request;
|
|
304
|
+
use `--input-file` for conversation history, function tools, or other API options:
|
|
305
|
+
|
|
306
|
+
```json
|
|
307
|
+
{
|
|
308
|
+
"messages": [
|
|
309
|
+
{"role": "user", "content": "What causes rainbows?"},
|
|
310
|
+
{"role": "assistant", "content": "Sunlight refracts and reflects in water droplets."},
|
|
311
|
+
{"role": "user", "content": "Why do the colors separate?"}
|
|
312
|
+
],
|
|
313
|
+
"max_completion_tokens": 4096
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
A positional prompt appends a user message to the supplied history. CLI options
|
|
318
|
+
override matching fields in the file. `--stream` displays text as it arrives;
|
|
319
|
+
JSON output returns one assembled response, including reasoning, tool calls,
|
|
320
|
+
and usage. Function calls are returned for the caller to handle. The MCP `chat`
|
|
321
|
+
tool accepts the same structured request and returns a complete response.
|
|
322
|
+
Use `--request-timeout` to change the chat HTTP timeout (default: 240 seconds).
|
|
323
|
+
|
|
284
324
|
### Visual Analysis
|
|
285
325
|
|
|
286
326
|
Analysis commands accept HTTP(S) URLs and local files. Local files are uploaded
|
|
@@ -399,7 +439,7 @@ Global options must appear before the command they apply to.
|
|
|
399
439
|
| `--output-json`, `-j` | Shortcut for JSON output |
|
|
400
440
|
| `--jq EXPRESSION` | Filter JSON output |
|
|
401
441
|
| `--wait`, `--no-wait` | Control async task polling |
|
|
402
|
-
| `--timeout SECONDS` | Set the polling timeout |
|
|
442
|
+
| `--timeout SECONDS` | Set the polling timeout (default: 30 minutes) |
|
|
403
443
|
| `--poll-interval SECONDS` | Set the delay between status requests |
|
|
404
444
|
| `--download DIRECTORY`, `-d DIRECTORY` | Download completed result files |
|
|
405
445
|
| `--no-color` | Disable colored terminal output |
|
|
@@ -490,7 +530,7 @@ everypixel --show-completion
|
|
|
490
530
|
- Authentication failure: run `everypixel auth check`, then configure the
|
|
491
531
|
credentials again if needed.
|
|
492
532
|
- Wait timeout: increase the global timeout, for example
|
|
493
|
-
`everypixel --timeout
|
|
533
|
+
`everypixel --timeout 7200 wait TASK_ID`.
|
|
494
534
|
- Invalid media: pass an existing local file, an HTTP(S) URL, or a supported
|
|
495
535
|
data URI.
|
|
496
536
|
- File not found: use an absolute path or run the command from the directory
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Everypixel CLI
|
|
2
2
|
|
|
3
3
|
Official command-line interface for the [Everypixel API](https://labs.everypixel.com/).
|
|
4
|
-
Generate and edit images and videos,
|
|
5
|
-
automate Everypixel workflows from a terminal or an MCP client.
|
|
4
|
+
Generate and edit images and videos, chat with GLM-5.3, work with audio, run
|
|
5
|
+
visual analysis, and automate Everypixel workflows from a terminal or an MCP client.
|
|
6
6
|
|
|
7
7
|
The CLI is designed for both people and agents: commands use readable terminal
|
|
8
8
|
output by default and can return stable JSON for scripts and tool integrations.
|
|
@@ -147,6 +147,15 @@ to select their aspect ratio. Raster models also accept `--resolution`.
|
|
|
147
147
|
|
|
148
148
|
`--lora-url` is supported only by `zimage`.
|
|
149
149
|
|
|
150
|
+
GPT Image models are `gpt-image-2` and `gpt-image-2.5-sunburst`. Both generation
|
|
151
|
+
and editing accept `--quality low|medium|high` (default: `medium`); Sunburst also
|
|
152
|
+
supports `xhigh` and `max`. Use `--size square` with `--resolution 3k`.
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
everypixel image generate "a detailed botanical illustration" \
|
|
156
|
+
--model gpt-image-2.5-sunburst --quality high --download ./outputs
|
|
157
|
+
```
|
|
158
|
+
|
|
150
159
|
Grok image models are `grok-imagine`, `grok-imagine-2`, and
|
|
151
160
|
`grok-imagine-2-low`. The `grok` alias remains supported. `grok_quality` and
|
|
152
161
|
`wan22` have been removed from the API.
|
|
@@ -247,6 +256,37 @@ everypixel audio tts-create \
|
|
|
247
256
|
--download ./outputs
|
|
248
257
|
```
|
|
249
258
|
|
|
259
|
+
### Chat
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
everypixel chat "Explain how a rainbow forms."
|
|
263
|
+
everypixel chat "Review this idea." --system "Be concise." --stream
|
|
264
|
+
everypixel chat "Solve this problem." --reasoning-effort high --show-reasoning
|
|
265
|
+
everypixel chat --input-file ./conversation.json --output-json
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Chat uses the same credentials as other commands and requires the client's
|
|
269
|
+
`chat` scope. The default model is `glm-5.3`. Each invocation sends one request;
|
|
270
|
+
use `--input-file` for conversation history, function tools, or other API options:
|
|
271
|
+
|
|
272
|
+
```json
|
|
273
|
+
{
|
|
274
|
+
"messages": [
|
|
275
|
+
{"role": "user", "content": "What causes rainbows?"},
|
|
276
|
+
{"role": "assistant", "content": "Sunlight refracts and reflects in water droplets."},
|
|
277
|
+
{"role": "user", "content": "Why do the colors separate?"}
|
|
278
|
+
],
|
|
279
|
+
"max_completion_tokens": 4096
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
A positional prompt appends a user message to the supplied history. CLI options
|
|
284
|
+
override matching fields in the file. `--stream` displays text as it arrives;
|
|
285
|
+
JSON output returns one assembled response, including reasoning, tool calls,
|
|
286
|
+
and usage. Function calls are returned for the caller to handle. The MCP `chat`
|
|
287
|
+
tool accepts the same structured request and returns a complete response.
|
|
288
|
+
Use `--request-timeout` to change the chat HTTP timeout (default: 240 seconds).
|
|
289
|
+
|
|
250
290
|
### Visual Analysis
|
|
251
291
|
|
|
252
292
|
Analysis commands accept HTTP(S) URLs and local files. Local files are uploaded
|
|
@@ -365,7 +405,7 @@ Global options must appear before the command they apply to.
|
|
|
365
405
|
| `--output-json`, `-j` | Shortcut for JSON output |
|
|
366
406
|
| `--jq EXPRESSION` | Filter JSON output |
|
|
367
407
|
| `--wait`, `--no-wait` | Control async task polling |
|
|
368
|
-
| `--timeout SECONDS` | Set the polling timeout |
|
|
408
|
+
| `--timeout SECONDS` | Set the polling timeout (default: 30 minutes) |
|
|
369
409
|
| `--poll-interval SECONDS` | Set the delay between status requests |
|
|
370
410
|
| `--download DIRECTORY`, `-d DIRECTORY` | Download completed result files |
|
|
371
411
|
| `--no-color` | Disable colored terminal output |
|
|
@@ -456,7 +496,7 @@ everypixel --show-completion
|
|
|
456
496
|
- Authentication failure: run `everypixel auth check`, then configure the
|
|
457
497
|
credentials again if needed.
|
|
458
498
|
- Wait timeout: increase the global timeout, for example
|
|
459
|
-
`everypixel --timeout
|
|
499
|
+
`everypixel --timeout 7200 wait TASK_ID`.
|
|
460
500
|
- Invalid media: pass an existing local file, an HTTP(S) URL, or a supported
|
|
461
501
|
data URI.
|
|
462
502
|
- File not found: use an absolute path or run the command from the directory
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.0"
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""Chat request construction and completion assembly without presentation code."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
from collections.abc import Callable, Iterator
|
|
7
|
+
from typing import Any, Protocol
|
|
8
|
+
|
|
9
|
+
from ..errors import APIResponseError, ValidationCLIError
|
|
10
|
+
from ..schemas import ChatRequest
|
|
11
|
+
from .models import ExecutionOptions, OperationResult
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ChatClientProtocol(Protocol):
|
|
15
|
+
def request(
|
|
16
|
+
self,
|
|
17
|
+
method: str,
|
|
18
|
+
path: str,
|
|
19
|
+
*,
|
|
20
|
+
json: dict[str, Any] | None = None,
|
|
21
|
+
params: dict[str, Any] | None = None,
|
|
22
|
+
files: dict[str, Any] | None = None,
|
|
23
|
+
auth_required: bool = True,
|
|
24
|
+
timeout: float | None = None,
|
|
25
|
+
) -> Any: ...
|
|
26
|
+
|
|
27
|
+
def stream_request(
|
|
28
|
+
self, method: str, path: str, *, json: dict[str, Any], timeout: float
|
|
29
|
+
) -> Iterator[dict[str, Any]]: ...
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def build_chat_payload(
|
|
33
|
+
*,
|
|
34
|
+
payload: dict[str, Any],
|
|
35
|
+
prompt: str | None,
|
|
36
|
+
system: str | None,
|
|
37
|
+
options: dict[str, Any],
|
|
38
|
+
) -> dict[str, Any]:
|
|
39
|
+
values = {
|
|
40
|
+
**payload,
|
|
41
|
+
**{key: value for key, value in options.items() if value is not None},
|
|
42
|
+
}
|
|
43
|
+
values.setdefault("model", "glm-5.3")
|
|
44
|
+
messages = values.get("messages", [])
|
|
45
|
+
if not isinstance(messages, list):
|
|
46
|
+
raise ValidationCLIError("messages must be an array")
|
|
47
|
+
messages = list(messages)
|
|
48
|
+
if system is not None:
|
|
49
|
+
messages.insert(0, {"role": "system", "content": system})
|
|
50
|
+
if prompt is not None:
|
|
51
|
+
messages.append({"role": "user", "content": prompt})
|
|
52
|
+
values["messages"] = messages
|
|
53
|
+
if values.get("stream") and "stream_options" not in values:
|
|
54
|
+
values["stream_options"] = {"include_usage": True}
|
|
55
|
+
return ChatRequest.model_validate(values).model_dump(mode="json", exclude_none=True)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ChatCompletion:
|
|
59
|
+
"""Combine text, reasoning, and tool-call fragments into a completion."""
|
|
60
|
+
|
|
61
|
+
def __init__(self) -> None:
|
|
62
|
+
self.metadata: dict[str, Any] = {}
|
|
63
|
+
self.choices: dict[int, dict[str, Any]] = {}
|
|
64
|
+
self.tools: dict[int, dict[int, dict[str, Any]]] = {}
|
|
65
|
+
|
|
66
|
+
def add(self, event: dict[str, Any]) -> None:
|
|
67
|
+
choices = event.get("choices")
|
|
68
|
+
if not isinstance(choices, list):
|
|
69
|
+
raise APIResponseError("API returned invalid chat choices")
|
|
70
|
+
self.metadata.update(
|
|
71
|
+
{key: value for key, value in event.items() if key != "choices"}
|
|
72
|
+
)
|
|
73
|
+
for item in choices:
|
|
74
|
+
if not isinstance(item, dict):
|
|
75
|
+
raise APIResponseError("API returned an invalid chat choice")
|
|
76
|
+
index = item.get("index")
|
|
77
|
+
delta = item.get("delta")
|
|
78
|
+
if type(index) is not int or index < 0 or not isinstance(delta, dict):
|
|
79
|
+
raise APIResponseError("API returned an invalid chat delta")
|
|
80
|
+
choice = self.choices.setdefault(
|
|
81
|
+
index,
|
|
82
|
+
{
|
|
83
|
+
"index": index,
|
|
84
|
+
"message": {"role": "assistant", "content": None},
|
|
85
|
+
"finish_reason": None,
|
|
86
|
+
},
|
|
87
|
+
)
|
|
88
|
+
choice.update(
|
|
89
|
+
{
|
|
90
|
+
key: value
|
|
91
|
+
for key, value in item.items()
|
|
92
|
+
if key not in {"delta", "finish_reason"}
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
if item.get("finish_reason") is not None:
|
|
96
|
+
choice["finish_reason"] = item["finish_reason"]
|
|
97
|
+
message = choice["message"]
|
|
98
|
+
for key in ("content", "reasoning_content", "refusal"):
|
|
99
|
+
append_fragment(message, delta, key)
|
|
100
|
+
if "role" in delta:
|
|
101
|
+
message["role"] = delta["role"]
|
|
102
|
+
tool_calls = delta.get("tool_calls")
|
|
103
|
+
if tool_calls is not None:
|
|
104
|
+
self._add_tools(index, tool_calls)
|
|
105
|
+
|
|
106
|
+
def _add_tools(self, choice_index: int, calls: Any) -> None:
|
|
107
|
+
if not isinstance(calls, list):
|
|
108
|
+
raise APIResponseError("API returned invalid tool calls")
|
|
109
|
+
tools = self.tools.setdefault(choice_index, {})
|
|
110
|
+
for call in calls:
|
|
111
|
+
if not isinstance(call, dict):
|
|
112
|
+
raise APIResponseError("API returned an invalid tool call")
|
|
113
|
+
index = call.get("index")
|
|
114
|
+
if type(index) is not int or index < 0:
|
|
115
|
+
raise APIResponseError("API returned an invalid tool call index")
|
|
116
|
+
tool = tools.setdefault(index, {"function": {"name": "", "arguments": ""}})
|
|
117
|
+
append_fragment(tool, call, "id")
|
|
118
|
+
if "type" in call:
|
|
119
|
+
tool["type"] = call["type"]
|
|
120
|
+
function = call.get("function")
|
|
121
|
+
if function is not None:
|
|
122
|
+
if not isinstance(function, dict):
|
|
123
|
+
raise APIResponseError("API returned an invalid function call")
|
|
124
|
+
for key in ("name", "arguments"):
|
|
125
|
+
append_fragment(tool["function"], function, key)
|
|
126
|
+
|
|
127
|
+
def result(self) -> dict[str, Any]:
|
|
128
|
+
if not self.choices or any(
|
|
129
|
+
choice["finish_reason"] is None for choice in self.choices.values()
|
|
130
|
+
):
|
|
131
|
+
raise APIResponseError("API returned an unfinished chat completion")
|
|
132
|
+
for index, tools in self.tools.items():
|
|
133
|
+
self.choices[index]["message"]["tool_calls"] = [
|
|
134
|
+
tools[key] for key in sorted(tools)
|
|
135
|
+
]
|
|
136
|
+
return {
|
|
137
|
+
**self.metadata,
|
|
138
|
+
"object": "chat.completion",
|
|
139
|
+
"choices": [self.choices[index] for index in sorted(self.choices)],
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def append_fragment(target: dict[str, Any], delta: dict[str, Any], key: str) -> None:
|
|
144
|
+
value = delta.get(key)
|
|
145
|
+
if value is None:
|
|
146
|
+
return
|
|
147
|
+
if not isinstance(value, str):
|
|
148
|
+
raise APIResponseError("API returned an invalid chat text fragment")
|
|
149
|
+
target[key] = (target.get(key) or "") + value
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class ChatService:
|
|
153
|
+
def __init__(self, client: ChatClientProtocol) -> None:
|
|
154
|
+
self.client = client
|
|
155
|
+
|
|
156
|
+
def execute(
|
|
157
|
+
self,
|
|
158
|
+
payload: dict[str, Any],
|
|
159
|
+
*,
|
|
160
|
+
request_timeout: float = 240.0,
|
|
161
|
+
execution: ExecutionOptions | None = None,
|
|
162
|
+
on_chunk: Callable[[dict[str, Any]], None] | None = None,
|
|
163
|
+
) -> OperationResult:
|
|
164
|
+
if not math.isfinite(request_timeout) or request_timeout <= 0:
|
|
165
|
+
raise ValidationCLIError("request timeout must be a finite positive number")
|
|
166
|
+
execution = execution or ExecutionOptions()
|
|
167
|
+
execution.check_cancelled()
|
|
168
|
+
if not payload.get("stream"):
|
|
169
|
+
value = self.client.request(
|
|
170
|
+
"POST", "/v1/chat/completions", json=payload, timeout=request_timeout
|
|
171
|
+
)
|
|
172
|
+
execution.check_cancelled()
|
|
173
|
+
return OperationResult(value=value)
|
|
174
|
+
completion = ChatCompletion()
|
|
175
|
+
events = self.client.stream_request(
|
|
176
|
+
"POST", "/v1/chat/completions", json=payload, timeout=request_timeout
|
|
177
|
+
)
|
|
178
|
+
# Closing the generator also closes its HTTP response on cancellation
|
|
179
|
+
# or a rendering failure, without starting another generation.
|
|
180
|
+
try:
|
|
181
|
+
for event in events:
|
|
182
|
+
execution.check_cancelled()
|
|
183
|
+
completion.add(event)
|
|
184
|
+
if on_chunk is not None:
|
|
185
|
+
on_chunk(event)
|
|
186
|
+
execution.check_cancelled()
|
|
187
|
+
finally:
|
|
188
|
+
close = getattr(events, "close", None)
|
|
189
|
+
if close is not None:
|
|
190
|
+
close()
|
|
191
|
+
execution.check_cancelled()
|
|
192
|
+
return OperationResult(value=completion.result())
|
|
@@ -33,6 +33,8 @@ from ..openapi import (
|
|
|
33
33
|
validate_payload_against_operation,
|
|
34
34
|
)
|
|
35
35
|
from ..schemas import (
|
|
36
|
+
ChatRequest,
|
|
37
|
+
GPT_IMAGE_MODEL_QUALITIES,
|
|
36
38
|
ImageEditPayload,
|
|
37
39
|
ImageGeneratePayload,
|
|
38
40
|
ImageUpscalePayload,
|
|
@@ -46,7 +48,9 @@ from ..schemas import (
|
|
|
46
48
|
VideoGenerateModel,
|
|
47
49
|
VideoUpscalePayload,
|
|
48
50
|
normalize_grok_model,
|
|
51
|
+
normalize_image_quality,
|
|
49
52
|
)
|
|
53
|
+
from .chat import ChatClientProtocol, ChatService, build_chat_payload
|
|
50
54
|
from .models import (
|
|
51
55
|
ExecutionOptions,
|
|
52
56
|
OperationCancelled,
|
|
@@ -61,22 +65,11 @@ _VIDEO_GENERATE_ADAPTER: TypeAdapter[VideoGenerateRequest] = TypeAdapter(
|
|
|
61
65
|
_VIDEO_EDIT_ADAPTER: TypeAdapter[VideoEditRequest] = TypeAdapter(VideoEditRequest)
|
|
62
66
|
|
|
63
67
|
|
|
64
|
-
class EverypixelClientProtocol(Protocol):
|
|
68
|
+
class EverypixelClientProtocol(ChatClientProtocol, Protocol):
|
|
65
69
|
"""Small infrastructure seam used by application services."""
|
|
66
70
|
|
|
67
71
|
base_url: str
|
|
68
72
|
|
|
69
|
-
def request(
|
|
70
|
-
self,
|
|
71
|
-
method: str,
|
|
72
|
-
path: str,
|
|
73
|
-
*,
|
|
74
|
-
json: dict[str, Any] | None = None,
|
|
75
|
-
params: dict[str, Any] | None = None,
|
|
76
|
-
files: dict[str, Any] | None = None,
|
|
77
|
-
auth_required: bool = True,
|
|
78
|
-
) -> Any: ...
|
|
79
|
-
|
|
80
73
|
def get_status(self, task_id: str) -> Any: ...
|
|
81
74
|
|
|
82
75
|
def check_auth(self) -> None: ...
|
|
@@ -837,6 +830,7 @@ class ApplicationServices:
|
|
|
837
830
|
execution: ExecutionOptions,
|
|
838
831
|
lora_url: str | None = None,
|
|
839
832
|
controls: dict[str, Any] | str | None = None,
|
|
833
|
+
quality: str | None = None,
|
|
840
834
|
) -> OperationResult:
|
|
841
835
|
payload = build_image_generate_payload(
|
|
842
836
|
prompt=prompt,
|
|
@@ -849,6 +843,7 @@ class ApplicationServices:
|
|
|
849
843
|
callback_url=callback_url,
|
|
850
844
|
lora_url=lora_url,
|
|
851
845
|
controls=controls,
|
|
846
|
+
quality=quality,
|
|
852
847
|
)
|
|
853
848
|
return self._execute_async(
|
|
854
849
|
endpoint="/v1/image_generate",
|
|
@@ -888,6 +883,7 @@ class ApplicationServices:
|
|
|
888
883
|
callback_url: str | None,
|
|
889
884
|
execution: ExecutionOptions,
|
|
890
885
|
megapixel_ratio: float = 1.0,
|
|
886
|
+
quality: str | None = None,
|
|
891
887
|
) -> OperationResult:
|
|
892
888
|
payload = build_image_edit_payload(
|
|
893
889
|
prompt=prompt,
|
|
@@ -896,6 +892,7 @@ class ApplicationServices:
|
|
|
896
892
|
image_size=image_size,
|
|
897
893
|
resolution=resolution,
|
|
898
894
|
megapixel_ratio=megapixel_ratio,
|
|
895
|
+
quality=quality,
|
|
899
896
|
seed=seed,
|
|
900
897
|
callback_url=callback_url,
|
|
901
898
|
)
|
|
@@ -1225,6 +1222,45 @@ class ApplicationServices:
|
|
|
1225
1222
|
fallback_extension=".mp3",
|
|
1226
1223
|
)
|
|
1227
1224
|
|
|
1225
|
+
def execute_chat(
|
|
1226
|
+
self,
|
|
1227
|
+
*,
|
|
1228
|
+
prompt: str | None = None,
|
|
1229
|
+
system: str | None = None,
|
|
1230
|
+
input_file: Path | None = None,
|
|
1231
|
+
payload: dict[str, Any] | None = None,
|
|
1232
|
+
model: str | None = None,
|
|
1233
|
+
stream: bool | None = None,
|
|
1234
|
+
max_completion_tokens: int | None = None,
|
|
1235
|
+
temperature: float | None = None,
|
|
1236
|
+
top_p: float | None = None,
|
|
1237
|
+
reasoning_effort: str | None = None,
|
|
1238
|
+
request_timeout: float = 240.0,
|
|
1239
|
+
execution: ExecutionOptions | None = None,
|
|
1240
|
+
on_chunk: Callable[[dict[str, Any]], None] | None = None,
|
|
1241
|
+
) -> OperationResult:
|
|
1242
|
+
inputs = parse_generic_payload(prompt=None, items=[], input_file=input_file)
|
|
1243
|
+
inputs.update(payload or {})
|
|
1244
|
+
request = build_chat_payload(
|
|
1245
|
+
payload=inputs,
|
|
1246
|
+
prompt=prompt,
|
|
1247
|
+
system=system,
|
|
1248
|
+
options={
|
|
1249
|
+
"model": model,
|
|
1250
|
+
"stream": stream,
|
|
1251
|
+
"max_completion_tokens": max_completion_tokens,
|
|
1252
|
+
"temperature": temperature,
|
|
1253
|
+
"top_p": top_p,
|
|
1254
|
+
"reasoning_effort": reasoning_effort,
|
|
1255
|
+
},
|
|
1256
|
+
)
|
|
1257
|
+
return ChatService(self._operations.client).execute(
|
|
1258
|
+
request,
|
|
1259
|
+
request_timeout=request_timeout,
|
|
1260
|
+
execution=execution,
|
|
1261
|
+
on_chunk=on_chunk,
|
|
1262
|
+
)
|
|
1263
|
+
|
|
1228
1264
|
def get_task_status(
|
|
1229
1265
|
self, *, task_id: str, execution: ExecutionOptions
|
|
1230
1266
|
) -> OperationResult:
|
|
@@ -1276,6 +1312,21 @@ class ApplicationServices:
|
|
|
1276
1312
|
)
|
|
1277
1313
|
if path in {"/v1/image_generate", "/v1/image_edit", "/v1/video_generate"}:
|
|
1278
1314
|
payload = normalize_grok_model(payload)
|
|
1315
|
+
if path in {"/v1/image_generate", "/v1/image_edit"}:
|
|
1316
|
+
try:
|
|
1317
|
+
payload = normalize_image_quality(payload)
|
|
1318
|
+
except ValueError as exc:
|
|
1319
|
+
raise ValidationCLIError(str(exc)) from exc
|
|
1320
|
+
if (
|
|
1321
|
+
isinstance(payload.get("model"), str)
|
|
1322
|
+
and payload["model"] in GPT_IMAGE_MODEL_QUALITIES
|
|
1323
|
+
):
|
|
1324
|
+
image_schema = (
|
|
1325
|
+
ImageGeneratePayload
|
|
1326
|
+
if path == "/v1/image_generate"
|
|
1327
|
+
else ImageEditPayload
|
|
1328
|
+
)
|
|
1329
|
+
image_schema.model_validate(payload, extra="ignore")
|
|
1279
1330
|
if path == "/v1/video_generate" and payload.get("model") in get_args(
|
|
1280
1331
|
VideoGenerateModel
|
|
1281
1332
|
):
|
|
@@ -1287,6 +1338,8 @@ class ApplicationServices:
|
|
|
1287
1338
|
# specialized model does not know yet; validate known semantics
|
|
1288
1339
|
# without rewriting or narrowing their payload.
|
|
1289
1340
|
_VIDEO_EDIT_ADAPTER.validate_python(payload, extra="ignore")
|
|
1341
|
+
if path == "/v1/chat/completions":
|
|
1342
|
+
ChatRequest.model_validate(payload, extra="ignore")
|
|
1290
1343
|
validate_payload_against_operation(operation, payload)
|
|
1291
1344
|
if dry_run:
|
|
1292
1345
|
return OperationResult(
|
|
@@ -1300,6 +1353,10 @@ class ApplicationServices:
|
|
|
1300
1353
|
"schema_source": schema_source,
|
|
1301
1354
|
}
|
|
1302
1355
|
)
|
|
1356
|
+
if path == "/v1/chat/completions" and resolved_method == "POST":
|
|
1357
|
+
return ChatService(self._operations.client).execute(
|
|
1358
|
+
payload, execution=execution
|
|
1359
|
+
)
|
|
1303
1360
|
return self._operations.execute(
|
|
1304
1361
|
OperationRequest(
|
|
1305
1362
|
endpoint=path,
|