echoact 0.1.0__py3-none-any.whl
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.
- echoact/__init__.py +3 -0
- echoact/__main__.py +117 -0
- echoact/app.py +315 -0
- echoact/audio/__init__.py +0 -0
- echoact/audio/devices.py +192 -0
- echoact/audio/player.py +611 -0
- echoact/audio/wav.py +854 -0
- echoact/config/__init__.py +0 -0
- echoact/config/budget.py +370 -0
- echoact/config/settings.py +1244 -0
- echoact/db/__init__.py +0 -0
- echoact/db/backup.py +2429 -0
- echoact/db/migrations.py +434 -0
- echoact/db/schema.sql +214 -0
- echoact/db/store.py +2062 -0
- echoact/diagnostics.py +902 -0
- echoact/domain.py +487 -0
- echoact/engine/__init__.py +0 -0
- echoact/engine/container.py +843 -0
- echoact/engine/protocol.py +241 -0
- echoact/engine/runtime.py +324 -0
- echoact/engine/supervisor.py +961 -0
- echoact/engine/worker.py +659 -0
- echoact/errors.py +281 -0
- echoact/instance.py +172 -0
- echoact/jobs/__init__.py +0 -0
- echoact/jobs/engine.py +776 -0
- echoact/jobs/request.py +300 -0
- echoact/mcp/__init__.py +0 -0
- echoact/mcp/__main__.py +50 -0
- echoact/mcp/client.py +202 -0
- echoact/mcp/config.py +112 -0
- echoact/mcp/server.py +340 -0
- echoact/models/__init__.py +0 -0
- echoact/models/catalog.py +273 -0
- echoact/models/manifest.py +278 -0
- echoact/models/registry.py +1551 -0
- echoact/paths.py +93 -0
- echoact/policy.py +189 -0
- echoact/security/__init__.py +0 -0
- echoact/security/credentials.py +930 -0
- echoact/security/ratelimit.py +534 -0
- echoact/service/__init__.py +20 -0
- echoact/service/app.py +182 -0
- echoact/service/deps.py +563 -0
- echoact/service/errors.py +241 -0
- echoact/service/routes.py +1125 -0
- echoact/service/schemas.py +509 -0
- echoact/service/server.py +270 -0
- echoact/text/__init__.py +0 -0
- echoact/text/language.py +44 -0
- echoact/text/loader.py +577 -0
- echoact/text/normalize.py +924 -0
- echoact/text/segment.py +499 -0
- echoact/text/sniff.py +1202 -0
- echoact/ui/__init__.py +0 -0
- echoact/ui/bridge.py +50 -0
- echoact/ui/controls.py +360 -0
- echoact/ui/credential_dialog.py +131 -0
- echoact/ui/fonts.py +94 -0
- echoact/ui/i18n.py +260 -0
- echoact/ui/icons.py +440 -0
- echoact/ui/library.py +1642 -0
- echoact/ui/licence.py +162 -0
- echoact/ui/main_window.py +1202 -0
- echoact/ui/mcp_setup.py +494 -0
- echoact/ui/models_view.py +1142 -0
- echoact/ui/notifications.py +202 -0
- echoact/ui/reading.py +494 -0
- echoact/ui/settings_view.py +2258 -0
- echoact/ui/status_view.py +1193 -0
- echoact/ui/theme.py +579 -0
- echoact/util/__init__.py +0 -0
- echoact/util/ids.py +62 -0
- echoact/util/logging.py +127 -0
- echoact-0.1.0.dist-info/METADATA +162 -0
- echoact-0.1.0.dist-info/RECORD +80 -0
- echoact-0.1.0.dist-info/WHEEL +4 -0
- echoact-0.1.0.dist-info/entry_points.txt +3 -0
- echoact-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
"""The wire shapes of Section 2.10's twelve operations.
|
|
2
|
+
|
|
3
|
+
These models exist for two reasons at once. They validate what arrives, and
|
|
4
|
+
they are what FastAPI turns into the machine-readable specification F-57
|
|
5
|
+
requires -- so a field that is absent here is absent from the contract, and
|
|
6
|
+
the twelve response models below are the whole of what a client may depend on.
|
|
7
|
+
|
|
8
|
+
Two rules run through all of them.
|
|
9
|
+
|
|
10
|
+
* Nothing carries a filesystem path. 4.2 says a result is "queried by a
|
|
11
|
+
permission-checkable identifier rather than an arbitrary path", and F-57
|
|
12
|
+
keeps internal paths out of responses, so audio is addressed by job and
|
|
13
|
+
segment identifier and the bytes arrive from a separate streaming route.
|
|
14
|
+
* Enumerated values are the domain's own ``StrEnum`` members. N-24 makes a
|
|
15
|
+
behaviour difference between the GUI, REST, and MCP a defect, and the
|
|
16
|
+
cheapest way to keep the vocabularies identical is to have one of them.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
24
|
+
|
|
25
|
+
from ..domain import (
|
|
26
|
+
Gender,
|
|
27
|
+
JobKind,
|
|
28
|
+
JobState,
|
|
29
|
+
Language,
|
|
30
|
+
RequestPath,
|
|
31
|
+
RetentionMode,
|
|
32
|
+
SpeakingStyle,
|
|
33
|
+
)
|
|
34
|
+
from ..policy import TEMPO_DEFAULT, TEMPO_MAX, TEMPO_MIN
|
|
35
|
+
|
|
36
|
+
#: Longest duplicate-prevention key accepted. 4.1 scopes the key per client
|
|
37
|
+
#: and keeps it for an hour; it is an identifier the caller mints, so the only
|
|
38
|
+
#: bound that matters is that it cannot itself become a payload.
|
|
39
|
+
MAX_IDEMPOTENCY_KEY_CHARS = 200
|
|
40
|
+
|
|
41
|
+
def _strict(**extra: Any) -> ConfigDict:
|
|
42
|
+
"""The configuration every model in this module shares.
|
|
43
|
+
|
|
44
|
+
``extra="forbid"`` is the request half of F-54's "unknown models or
|
|
45
|
+
options are not arbitrarily substituted". Dropping an option this
|
|
46
|
+
version does not define substitutes its default just as surely as
|
|
47
|
+
replacing it would: a caller that wrote ``retainn`` gets a one-off job
|
|
48
|
+
whose text and audio 4.1 removes an hour after it ends, a caller that
|
|
49
|
+
wrote ``speed`` gets 1.0x, and neither is told. Section 2.10 is the
|
|
50
|
+
surface an automated client depends on, so an unrecognised word costs one
|
|
51
|
+
round trip and is named, rather than being silently reinterpreted.
|
|
52
|
+
"""
|
|
53
|
+
return ConfigDict(extra="forbid", protected_namespaces=(), **extra)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
_STRICT = _strict()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ======================================================================
|
|
60
|
+
# Requests
|
|
61
|
+
# ======================================================================
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class VoiceIn(BaseModel):
|
|
65
|
+
"""F-04 to F-08's settings, stated in full.
|
|
66
|
+
|
|
67
|
+
Nothing defaults to the owner's current GUI selection. F-54 forbids
|
|
68
|
+
substituting an unknown model or option, and silently inheriting the
|
|
69
|
+
desktop's chosen voice would make an automated caller's output depend on
|
|
70
|
+
what the person at the keyboard last clicked.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
model_config = _STRICT
|
|
74
|
+
|
|
75
|
+
model_id: str = Field(description="Model identifier from GET /api/v1/models.")
|
|
76
|
+
voice_id: str = Field(description="Voice identifier belonging to that model.")
|
|
77
|
+
gender: Gender = Field(description="Must match the voice's own gender (F-06).")
|
|
78
|
+
language: Language = Field(
|
|
79
|
+
default=Language.AUTO,
|
|
80
|
+
description="'auto' resolves per sentence on whether it contains Hangul.",
|
|
81
|
+
)
|
|
82
|
+
style: SpeakingStyle = Field(default=SpeakingStyle.NATURAL)
|
|
83
|
+
tempo: float = Field(default=TEMPO_DEFAULT, ge=TEMPO_MIN, le=TEMPO_MAX)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
_EXAMPLE_VOICE = {
|
|
87
|
+
"model_id": "supertonic-3",
|
|
88
|
+
"voice_id": "F1",
|
|
89
|
+
"gender": "female",
|
|
90
|
+
"language": "auto",
|
|
91
|
+
"style": "narration",
|
|
92
|
+
"tempo": 1.0,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class EstimateRequest(BaseModel):
|
|
97
|
+
"""F-88's pre-flight body. Creates no job and loads no model."""
|
|
98
|
+
|
|
99
|
+
model_config = _strict(
|
|
100
|
+
json_schema_extra={
|
|
101
|
+
"examples": [
|
|
102
|
+
{"kind": "speech", "text": "안녕하세요. 반갑습니다.", "voice": _EXAMPLE_VOICE}
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
text: str = Field(description="The text that would be spoken.")
|
|
108
|
+
voice: VoiceIn
|
|
109
|
+
kind: JobKind = Field(default=JobKind.SPEECH)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class CreateJobRequest(BaseModel):
|
|
113
|
+
"""F-54's generation request.
|
|
114
|
+
|
|
115
|
+
``idempotency_key`` is required rather than optional: F-49 refuses a
|
|
116
|
+
request without one, because with a single generation slot a client that
|
|
117
|
+
retries after a slow response would otherwise queue a second rendering of
|
|
118
|
+
the same text behind the first.
|
|
119
|
+
|
|
120
|
+
``text`` is absent when the request arrived as a multipart upload; F-54
|
|
121
|
+
makes the two alternatives and forbids both at once.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
model_config = _strict(
|
|
125
|
+
json_schema_extra={
|
|
126
|
+
"examples": [
|
|
127
|
+
{
|
|
128
|
+
"kind": "speech",
|
|
129
|
+
"idempotency_key": "2f8c1d5e-report-2026-09-11",
|
|
130
|
+
"text": "EchoAct reads a document aloud.",
|
|
131
|
+
"voice": _EXAMPLE_VOICE,
|
|
132
|
+
"retain": False,
|
|
133
|
+
"wait_s": 10,
|
|
134
|
+
}
|
|
135
|
+
]
|
|
136
|
+
},
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
kind: JobKind = Field(description="Exactly 'speech' in this release (F-54).")
|
|
140
|
+
idempotency_key: str = Field(
|
|
141
|
+
min_length=1,
|
|
142
|
+
max_length=MAX_IDEMPOTENCY_KEY_CHARS,
|
|
143
|
+
description="Duplicate-prevention key, scoped to this client (F-49).",
|
|
144
|
+
)
|
|
145
|
+
voice: VoiceIn
|
|
146
|
+
text: str | None = Field(
|
|
147
|
+
default=None, description="Inline text. Omit when uploading a file instead."
|
|
148
|
+
)
|
|
149
|
+
retain: bool = Field(
|
|
150
|
+
default=False,
|
|
151
|
+
description="Keep the job, its snapshot, and its audio in history (F-42).",
|
|
152
|
+
)
|
|
153
|
+
wait_s: float | None = Field(
|
|
154
|
+
default=None,
|
|
155
|
+
description=(
|
|
156
|
+
"Seconds to wait for a terminal state before answering (F-88). "
|
|
157
|
+
"Clamped to the service's ceiling; the job is unaffected either way."
|
|
158
|
+
),
|
|
159
|
+
)
|
|
160
|
+
encoding: str | None = Field(
|
|
161
|
+
default=None,
|
|
162
|
+
description="F-34's confirmed encoding, honoured only for an upload whose own is unclear.",
|
|
163
|
+
)
|
|
164
|
+
confirm_unknown: bool = Field(
|
|
165
|
+
default=False,
|
|
166
|
+
description="F-35's confirmation that an upload with no known extension is text.",
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
# ======================================================================
|
|
171
|
+
# Shared projections
|
|
172
|
+
# ======================================================================
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class ErrorBody(BaseModel):
|
|
176
|
+
"""F-57's error shape. Carries no path, no credential, and no body text."""
|
|
177
|
+
|
|
178
|
+
model_config = _STRICT
|
|
179
|
+
|
|
180
|
+
code: str
|
|
181
|
+
message: str
|
|
182
|
+
retryable: bool
|
|
183
|
+
request_id: str
|
|
184
|
+
detail: dict[str, Any] | None = None
|
|
185
|
+
retry_after_s: float | None = None
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class VoiceOut(BaseModel):
|
|
189
|
+
model_config = _STRICT
|
|
190
|
+
|
|
191
|
+
model_id: str
|
|
192
|
+
voice_id: str
|
|
193
|
+
gender: Gender
|
|
194
|
+
language: Language
|
|
195
|
+
style: SpeakingStyle
|
|
196
|
+
tempo: float
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class BudgetOut(BaseModel):
|
|
200
|
+
"""F-78's "value actually applied", never the value on the owner's screen."""
|
|
201
|
+
|
|
202
|
+
model_config = _STRICT
|
|
203
|
+
|
|
204
|
+
cpu_percent: int
|
|
205
|
+
memory_bytes: int
|
|
206
|
+
intra_op_threads: int
|
|
207
|
+
inter_op_threads: int
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class JobError(BaseModel):
|
|
211
|
+
model_config = _STRICT
|
|
212
|
+
|
|
213
|
+
code: str
|
|
214
|
+
message: str | None = None
|
|
215
|
+
retryable: bool
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class JobProgress(BaseModel):
|
|
219
|
+
model_config = _STRICT
|
|
220
|
+
|
|
221
|
+
generated_segments: int
|
|
222
|
+
total_segments: int
|
|
223
|
+
fraction: float
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class JobOut(BaseModel):
|
|
227
|
+
"""One job as F-54 and F-56 present it. Never the source text: F-56 puts
|
|
228
|
+
the snapshot behind its own separately authorised request."""
|
|
229
|
+
|
|
230
|
+
model_config = _STRICT
|
|
231
|
+
|
|
232
|
+
job_id: str
|
|
233
|
+
kind: JobKind
|
|
234
|
+
state: JobState
|
|
235
|
+
terminal: bool
|
|
236
|
+
request_path: RequestPath
|
|
237
|
+
owner_client_id: str
|
|
238
|
+
client_label: str | None
|
|
239
|
+
retention: RetentionMode
|
|
240
|
+
voice: VoiceOut
|
|
241
|
+
budget: BudgetOut | None
|
|
242
|
+
progress: JobProgress
|
|
243
|
+
created_at: float
|
|
244
|
+
started_at: float | None
|
|
245
|
+
ended_at: float | None
|
|
246
|
+
result_ready: bool
|
|
247
|
+
result_expired: bool
|
|
248
|
+
audio_duration_ms: int
|
|
249
|
+
error: JobError | None = None
|
|
250
|
+
#: Present only on a response to POST /api/v1/jobs. F-88 says waiting is
|
|
251
|
+
#: an optimisation, so a caller has to be able to tell that it happened
|
|
252
|
+
#: without inferring it from the elapsed time.
|
|
253
|
+
waited_s: float | None = None
|
|
254
|
+
#: True when this response returned an existing job for a repeated
|
|
255
|
+
#: duplicate-prevention key rather than creating one (F-49).
|
|
256
|
+
duplicate: bool | None = None
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class JobPage(BaseModel):
|
|
260
|
+
"""4.1's list page: 20 items by default, 100 at most."""
|
|
261
|
+
|
|
262
|
+
model_config = _STRICT
|
|
263
|
+
|
|
264
|
+
items: list[JobOut]
|
|
265
|
+
total: int
|
|
266
|
+
limit: int
|
|
267
|
+
offset: int
|
|
268
|
+
has_more: bool
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
class JobTextOut(BaseModel):
|
|
272
|
+
"""F-56's separately authorised snapshot."""
|
|
273
|
+
|
|
274
|
+
model_config = _STRICT
|
|
275
|
+
|
|
276
|
+
job_id: str
|
|
277
|
+
text: str
|
|
278
|
+
codepoints: int
|
|
279
|
+
retention: RetentionMode
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class SegmentOut(BaseModel):
|
|
283
|
+
"""F-55's per-segment mapping.
|
|
284
|
+
|
|
285
|
+
``source_start``/``source_end`` are Unicode code points into the job's
|
|
286
|
+
source text, start-inclusive and end-exclusive (4.2), and are never UI
|
|
287
|
+
indices. Times are milliseconds from the start of the job's audio.
|
|
288
|
+
"""
|
|
289
|
+
|
|
290
|
+
model_config = _STRICT
|
|
291
|
+
|
|
292
|
+
segment_id: str
|
|
293
|
+
index: int
|
|
294
|
+
source_start: int
|
|
295
|
+
source_end: int
|
|
296
|
+
start_ms: int
|
|
297
|
+
end_ms: int
|
|
298
|
+
duration_ms: int
|
|
299
|
+
language: str
|
|
300
|
+
spoken: bool
|
|
301
|
+
has_audio: bool
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
class SegmentsOut(BaseModel):
|
|
305
|
+
model_config = _STRICT
|
|
306
|
+
|
|
307
|
+
job_id: str
|
|
308
|
+
segments: list[SegmentOut]
|
|
309
|
+
#: The index of the last ready segment, or null when none is ready. A
|
|
310
|
+
#: caller polls with this rather than re-reading the whole list.
|
|
311
|
+
last_sequence: int | None
|
|
312
|
+
ready_count: int
|
|
313
|
+
total_segments: int
|
|
314
|
+
complete: bool
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
class IntegrityOut(BaseModel):
|
|
318
|
+
"""4.2's "integrity verification data", named rather than bare."""
|
|
319
|
+
|
|
320
|
+
model_config = _STRICT
|
|
321
|
+
|
|
322
|
+
algorithm: str
|
|
323
|
+
value: str
|
|
324
|
+
state: str
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
class ResultOut(BaseModel):
|
|
328
|
+
"""4.2's Result entity, as F-55 hands it to a client."""
|
|
329
|
+
|
|
330
|
+
model_config = _STRICT
|
|
331
|
+
|
|
332
|
+
job_id: str
|
|
333
|
+
result_id: str
|
|
334
|
+
#: ``echoact.audio.wav.FORMAT_NAME``. F-82 fixes one output format and
|
|
335
|
+
#: this is its name, rather than a second vocabulary for the same thing.
|
|
336
|
+
format: str
|
|
337
|
+
media_type: str
|
|
338
|
+
sample_rate: int
|
|
339
|
+
channels: int
|
|
340
|
+
sample_width_bits: int
|
|
341
|
+
frame_count: int
|
|
342
|
+
duration_ms: int
|
|
343
|
+
byte_size: int
|
|
344
|
+
integrity: IntegrityOut
|
|
345
|
+
created_at: float
|
|
346
|
+
expires_at: float | None
|
|
347
|
+
expired: bool
|
|
348
|
+
available: bool
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
class EstimateOut(BaseModel):
|
|
352
|
+
"""F-88's answer. ``approximate`` is always true and says so on the wire."""
|
|
353
|
+
|
|
354
|
+
model_config = _STRICT
|
|
355
|
+
|
|
356
|
+
valid: bool
|
|
357
|
+
codepoints: int
|
|
358
|
+
segment_count: int
|
|
359
|
+
audio_ms: int
|
|
360
|
+
synthesis_ms: int
|
|
361
|
+
first_audio_ms: int
|
|
362
|
+
slot_free: bool
|
|
363
|
+
model_ready: bool
|
|
364
|
+
approximate: bool
|
|
365
|
+
problems: list[dict[str, Any]]
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
class VoiceChoice(BaseModel):
|
|
369
|
+
"""F-53: a bare identifier gives a caller that is not a person no basis
|
|
370
|
+
for choosing, so the manifest's description travels with it."""
|
|
371
|
+
|
|
372
|
+
model_config = _STRICT
|
|
373
|
+
|
|
374
|
+
voice_id: str
|
|
375
|
+
display_name: str
|
|
376
|
+
gender: Gender
|
|
377
|
+
description: str
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
class LicenseOut(BaseModel):
|
|
381
|
+
model_config = _STRICT
|
|
382
|
+
|
|
383
|
+
name: str
|
|
384
|
+
acceptance_required: bool
|
|
385
|
+
accepted: bool
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
class MinimumBudgetOut(BaseModel):
|
|
389
|
+
model_config = _STRICT
|
|
390
|
+
|
|
391
|
+
memory_bytes: int
|
|
392
|
+
cpu_percent: int
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
class ModelOut(BaseModel):
|
|
396
|
+
"""F-53's model entry and F-63's row.
|
|
397
|
+
|
|
398
|
+
``state`` distinguishes an undownloaded model from a server fault, which
|
|
399
|
+
F-53 requires outright, and ``unavailable_reason`` carries F-04's reason
|
|
400
|
+
rather than hiding a model that cannot run.
|
|
401
|
+
"""
|
|
402
|
+
|
|
403
|
+
model_config = _STRICT
|
|
404
|
+
|
|
405
|
+
model_id: str
|
|
406
|
+
display_name: str
|
|
407
|
+
state: str
|
|
408
|
+
ready: bool
|
|
409
|
+
runnable: bool
|
|
410
|
+
unavailable_reason: str | None
|
|
411
|
+
sample_rate: int
|
|
412
|
+
languages: list[Language]
|
|
413
|
+
styles: list[SpeakingStyle]
|
|
414
|
+
voices: list[VoiceChoice]
|
|
415
|
+
minimum_budget: MinimumBudgetOut
|
|
416
|
+
bytes_total: int
|
|
417
|
+
bytes_present: int
|
|
418
|
+
license: LicenseOut
|
|
419
|
+
download_authorised: bool
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
class ModelsOut(BaseModel):
|
|
423
|
+
model_config = _STRICT
|
|
424
|
+
|
|
425
|
+
models: list[ModelOut]
|
|
426
|
+
languages: list[Language]
|
|
427
|
+
styles: list[SpeakingStyle]
|
|
428
|
+
genders: list[Gender]
|
|
429
|
+
input_formats: list[str]
|
|
430
|
+
encodings: list[str]
|
|
431
|
+
tempo_min: float
|
|
432
|
+
tempo_max: float
|
|
433
|
+
tempo_default: float
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
class ResourcePolicyOut(BaseModel):
|
|
437
|
+
"""Section 4.1's limits, as the actual numbers in force.
|
|
438
|
+
|
|
439
|
+
F-53 asks for "the actual resource policy", which is why the applied
|
|
440
|
+
budget is here beside the fixed limits: the ceilings are constants, but
|
|
441
|
+
what a job may use depends on the machine at this moment (F-21, N-04).
|
|
442
|
+
"""
|
|
443
|
+
|
|
444
|
+
model_config = _STRICT
|
|
445
|
+
|
|
446
|
+
max_input_codepoints: int
|
|
447
|
+
max_upload_bytes: int
|
|
448
|
+
max_request_body_bytes: int
|
|
449
|
+
concurrent_generation: int
|
|
450
|
+
generation_requests_per_minute: int
|
|
451
|
+
other_requests_per_minute: int
|
|
452
|
+
list_page_default: int
|
|
453
|
+
list_page_max: int
|
|
454
|
+
bounded_wait_default_s: float
|
|
455
|
+
bounded_wait_max_s: float
|
|
456
|
+
one_off_result_ttl_s: float
|
|
457
|
+
idempotency_ttl_s: float
|
|
458
|
+
tempo_min: float
|
|
459
|
+
tempo_max: float
|
|
460
|
+
applied_budget: BudgetOut | None
|
|
461
|
+
budget_unavailable_reason: str | None
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
class CapabilitiesOut(BaseModel):
|
|
465
|
+
model_config = _STRICT
|
|
466
|
+
|
|
467
|
+
granted: list[str]
|
|
468
|
+
job_kinds: list[JobKind]
|
|
469
|
+
languages: list[Language]
|
|
470
|
+
styles: list[SpeakingStyle]
|
|
471
|
+
input_formats: list[str]
|
|
472
|
+
bounded_wait: bool
|
|
473
|
+
estimate: bool
|
|
474
|
+
upload: bool
|
|
475
|
+
history: bool
|
|
476
|
+
results: bool
|
|
477
|
+
generation: bool
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
class GenerationStateOut(BaseModel):
|
|
481
|
+
"""F-47's single slot, as a caller needs to see it before asking."""
|
|
482
|
+
|
|
483
|
+
model_config = _STRICT
|
|
484
|
+
|
|
485
|
+
busy: bool
|
|
486
|
+
#: Set only when the running job belongs to this caller; N-19 keeps
|
|
487
|
+
#: another owner's job identifier out of a status response.
|
|
488
|
+
current_job_id: str | None
|
|
489
|
+
current_job_is_mine: bool
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
class StatusOut(BaseModel):
|
|
493
|
+
model_config = _STRICT
|
|
494
|
+
|
|
495
|
+
service: str
|
|
496
|
+
version: str
|
|
497
|
+
api_version: str
|
|
498
|
+
state: str
|
|
499
|
+
started_at: float
|
|
500
|
+
uptime_s: float
|
|
501
|
+
bind_host: str
|
|
502
|
+
bind_port: int
|
|
503
|
+
mcp_enabled: bool
|
|
504
|
+
client_id: str
|
|
505
|
+
client_name: str
|
|
506
|
+
credential_expires_at: float
|
|
507
|
+
capabilities: CapabilitiesOut
|
|
508
|
+
resource_policy: ResourcePolicyOut
|
|
509
|
+
generation: GenerationStateOut
|