everypixel-cli 0.2.2__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.2 → everypixel_cli-0.3.0}/CHANGELOG.md +5 -0
- {everypixel_cli-0.2.2 → everypixel_cli-0.3.0}/PKG-INFO +43 -3
- {everypixel_cli-0.2.2 → everypixel_cli-0.3.0}/README.md +42 -2
- 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.2 → everypixel_cli-0.3.0}/src/everypixel_cli/application/services.py +69 -12
- {everypixel_cli-0.2.2 → everypixel_cli-0.3.0}/src/everypixel_cli/cli.py +105 -3
- {everypixel_cli-0.2.2 → everypixel_cli-0.3.0}/src/everypixel_cli/client.py +89 -17
- {everypixel_cli-0.2.2 → everypixel_cli-0.3.0}/src/everypixel_cli/mcp_server.py +33 -0
- {everypixel_cli-0.2.2 → everypixel_cli-0.3.0}/src/everypixel_cli/output.py +55 -0
- {everypixel_cli-0.2.2 → everypixel_cli-0.3.0}/src/everypixel_cli/resources/openapi.json +567 -9
- {everypixel_cli-0.2.2 → everypixel_cli-0.3.0}/src/everypixel_cli/schemas.py +147 -18
- everypixel_cli-0.2.2/src/everypixel_cli/__init__.py +0 -1
- {everypixel_cli-0.2.2 → everypixel_cli-0.3.0}/.gitignore +0 -0
- {everypixel_cli-0.2.2 → everypixel_cli-0.3.0}/LICENSE +0 -0
- {everypixel_cli-0.2.2 → everypixel_cli-0.3.0}/pyproject.toml +0 -0
- {everypixel_cli-0.2.2 → everypixel_cli-0.3.0}/src/everypixel_cli/__main__.py +0 -0
- {everypixel_cli-0.2.2 → everypixel_cli-0.3.0}/src/everypixel_cli/application/__init__.py +0 -0
- {everypixel_cli-0.2.2 → everypixel_cli-0.3.0}/src/everypixel_cli/application/models.py +0 -0
- {everypixel_cli-0.2.2 → everypixel_cli-0.3.0}/src/everypixel_cli/application/serialization.py +0 -0
- {everypixel_cli-0.2.2 → everypixel_cli-0.3.0}/src/everypixel_cli/config.py +0 -0
- {everypixel_cli-0.2.2 → everypixel_cli-0.3.0}/src/everypixel_cli/errors.py +0 -0
- {everypixel_cli-0.2.2 → everypixel_cli-0.3.0}/src/everypixel_cli/files.py +0 -0
- {everypixel_cli-0.2.2 → everypixel_cli-0.3.0}/src/everypixel_cli/openapi.py +0 -0
- {everypixel_cli-0.2.2 → everypixel_cli-0.3.0}/src/everypixel_cli/resources/__init__.py +0 -0
|
@@ -1,5 +1,10 @@
|
|
|
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
|
+
|
|
3
8
|
## [0.2.2] - 2026-09-11
|
|
4
9
|
|
|
5
10
|
- Increase the default task wait timeout to 30 minutes for long video generation.
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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,
|
|
@@ -48,7 +48,7 @@ from .errors import (
|
|
|
48
48
|
normalize_exception,
|
|
49
49
|
)
|
|
50
50
|
from .openapi import load_schema
|
|
51
|
-
from .output import emit_error, emit_human, emit_json
|
|
51
|
+
from .output import ChatRenderer, emit_error, emit_human, emit_json
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
def json_output_requested(args: Sequence[str]) -> bool:
|
|
@@ -395,11 +395,20 @@ def stop_with_error(ctx: typer.Context, exc: Exception) -> NoReturn:
|
|
|
395
395
|
raise typer.Exit(error.exit_code) from exc
|
|
396
396
|
|
|
397
397
|
|
|
398
|
-
def run_action(
|
|
398
|
+
def run_action(
|
|
399
|
+
ctx: typer.Context,
|
|
400
|
+
action: Callable[[], Any],
|
|
401
|
+
*,
|
|
402
|
+
render: Callable[[Any], None] | None = None,
|
|
403
|
+
) -> None:
|
|
399
404
|
"""Run a command action and handle all expected application errors."""
|
|
400
405
|
|
|
401
406
|
try:
|
|
402
|
-
|
|
407
|
+
value = action()
|
|
408
|
+
if render is None:
|
|
409
|
+
finish(ctx, value)
|
|
410
|
+
else:
|
|
411
|
+
render(value)
|
|
403
412
|
except typer.Exit:
|
|
404
413
|
raise
|
|
405
414
|
except (
|
|
@@ -430,6 +439,83 @@ def apply_common_options(
|
|
|
430
439
|
return runtime
|
|
431
440
|
|
|
432
441
|
|
|
442
|
+
@app.command("chat")
|
|
443
|
+
def chat(
|
|
444
|
+
ctx: typer.Context,
|
|
445
|
+
prompt: Annotated[
|
|
446
|
+
Optional[str], typer.Argument(help="User message to append.")
|
|
447
|
+
] = None,
|
|
448
|
+
system: Annotated[
|
|
449
|
+
Optional[str], typer.Option("--system", help="System instruction.")
|
|
450
|
+
] = None,
|
|
451
|
+
input_file: Annotated[
|
|
452
|
+
Optional[Path],
|
|
453
|
+
typer.Option(
|
|
454
|
+
"--input-file",
|
|
455
|
+
help="JSON request with messages, tools, and other chat options.",
|
|
456
|
+
),
|
|
457
|
+
] = None,
|
|
458
|
+
model: Annotated[
|
|
459
|
+
Optional[str], typer.Option("--model", help="Chat model (default: glm-5.3).")
|
|
460
|
+
] = None,
|
|
461
|
+
stream: Annotated[
|
|
462
|
+
Optional[bool],
|
|
463
|
+
typer.Option("--stream/--no-stream", help="Stream the response."),
|
|
464
|
+
] = None,
|
|
465
|
+
max_completion_tokens: Annotated[
|
|
466
|
+
Optional[int], typer.Option("--max-completion-tokens")
|
|
467
|
+
] = None,
|
|
468
|
+
temperature: Annotated[Optional[float], typer.Option("--temperature")] = None,
|
|
469
|
+
top_p: Annotated[Optional[float], typer.Option("--top-p")] = None,
|
|
470
|
+
reasoning_effort: Annotated[
|
|
471
|
+
Optional[str], typer.Option("--reasoning-effort", help="low, high, or max.")
|
|
472
|
+
] = None,
|
|
473
|
+
show_reasoning: Annotated[
|
|
474
|
+
bool,
|
|
475
|
+
typer.Option("--show-reasoning", help="Include reasoning in human output."),
|
|
476
|
+
] = False,
|
|
477
|
+
request_timeout: Annotated[
|
|
478
|
+
float,
|
|
479
|
+
typer.Option(
|
|
480
|
+
"--request-timeout",
|
|
481
|
+
callback=positive_float,
|
|
482
|
+
help="HTTP timeout in seconds.",
|
|
483
|
+
),
|
|
484
|
+
] = 240.0,
|
|
485
|
+
output_json: OutputJsonOption = False,
|
|
486
|
+
jq_expr: JqOption = None,
|
|
487
|
+
) -> None:
|
|
488
|
+
"""Chat with GLM-5.3 using a prompt or a JSON conversation."""
|
|
489
|
+
|
|
490
|
+
runtime = apply_common_options(ctx, output_json=output_json, jq_expr=jq_expr)
|
|
491
|
+
renderer = ChatRenderer(show_reasoning=show_reasoning, no_color=runtime.no_color)
|
|
492
|
+
|
|
493
|
+
def render(result: Any) -> None:
|
|
494
|
+
if runtime.output_json:
|
|
495
|
+
finish(ctx, result)
|
|
496
|
+
else:
|
|
497
|
+
renderer.finish(serialize_operation_result(result))
|
|
498
|
+
|
|
499
|
+
run_action(
|
|
500
|
+
ctx,
|
|
501
|
+
lambda: runtime.services().execute_chat(
|
|
502
|
+
prompt=prompt,
|
|
503
|
+
system=system,
|
|
504
|
+
input_file=input_file,
|
|
505
|
+
model=model,
|
|
506
|
+
stream=stream,
|
|
507
|
+
max_completion_tokens=max_completion_tokens,
|
|
508
|
+
temperature=temperature,
|
|
509
|
+
top_p=top_p,
|
|
510
|
+
reasoning_effort=reasoning_effort,
|
|
511
|
+
request_timeout=request_timeout,
|
|
512
|
+
execution=runtime.execution_options(),
|
|
513
|
+
on_chunk=None if runtime.output_json else renderer.chunk,
|
|
514
|
+
),
|
|
515
|
+
render=render,
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
|
|
433
519
|
@auth_app.command("configure")
|
|
434
520
|
def auth_configure(
|
|
435
521
|
ctx: typer.Context,
|
|
@@ -584,6 +670,13 @@ def image_generate(
|
|
|
584
670
|
typer.Option("--controls", help="Recraft palette controls as JSON."),
|
|
585
671
|
] = None,
|
|
586
672
|
resolution: Annotated[Optional[str], typer.Option("--resolution")] = None,
|
|
673
|
+
quality: Annotated[
|
|
674
|
+
Optional[str],
|
|
675
|
+
typer.Option(
|
|
676
|
+
"--quality",
|
|
677
|
+
help="GPT Image quality: low, medium, high; 2.5 also xhigh, max.",
|
|
678
|
+
),
|
|
679
|
+
] = None,
|
|
587
680
|
seed: Annotated[int, typer.Option("--seed")] = -1,
|
|
588
681
|
callback_url: Annotated[Optional[str], typer.Option("--callback-url")] = None,
|
|
589
682
|
wait_option: WaitOption = None,
|
|
@@ -611,6 +704,7 @@ def image_generate(
|
|
|
611
704
|
lora_url=lora_url,
|
|
612
705
|
controls=controls,
|
|
613
706
|
resolution=resolution,
|
|
707
|
+
quality=quality,
|
|
614
708
|
seed=seed,
|
|
615
709
|
callback_url=callback_url,
|
|
616
710
|
execution=runtime.execution_options(),
|
|
@@ -630,6 +724,13 @@ def image_edit(
|
|
|
630
724
|
size: Annotated[Optional[str], typer.Option("--size")] = None,
|
|
631
725
|
resolution: Annotated[Optional[str], typer.Option("--resolution")] = None,
|
|
632
726
|
megapixel_ratio: Annotated[float, typer.Option("--megapixel-ratio")] = 1.0,
|
|
727
|
+
quality: Annotated[
|
|
728
|
+
Optional[str],
|
|
729
|
+
typer.Option(
|
|
730
|
+
"--quality",
|
|
731
|
+
help="GPT Image quality: low, medium, high; 2.5 also xhigh, max.",
|
|
732
|
+
),
|
|
733
|
+
] = None,
|
|
633
734
|
seed: Annotated[int, typer.Option("--seed")] = -1,
|
|
634
735
|
callback_url: Annotated[Optional[str], typer.Option("--callback-url")] = None,
|
|
635
736
|
wait_option: WaitOption = None,
|
|
@@ -655,6 +756,7 @@ def image_edit(
|
|
|
655
756
|
image_size=size,
|
|
656
757
|
resolution=resolution,
|
|
657
758
|
megapixel_ratio=megapixel_ratio,
|
|
759
|
+
quality=quality,
|
|
658
760
|
seed=seed,
|
|
659
761
|
callback_url=callback_url,
|
|
660
762
|
execution=runtime.execution_options(),
|