zettcode-server 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.
- zettcode_server/__init__.py +33 -0
- zettcode_server/app.py +580 -0
- zettcode_server/cli.py +114 -0
- zettcode_server/runner.py +472 -0
- zettcode_server/schemas.py +555 -0
- zettcode_server/serialize.py +292 -0
- zettcode_server/settings.py +58 -0
- zettcode_server-0.1.0.dist-info/METADATA +66 -0
- zettcode_server-0.1.0.dist-info/RECORD +11 -0
- zettcode_server-0.1.0.dist-info/WHEEL +4 -0
- zettcode_server-0.1.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
"""The shapes the HTTP API promises, so a client has a schema and not a guess.
|
|
2
|
+
|
|
3
|
+
These are the server's own types. The SDK's values are converted into them at the edge (see
|
|
4
|
+
:mod:`zettcode_server.serialize`), which keeps the wire format a decision of this package: the SDK may gain a field
|
|
5
|
+
without changing what a client sees, and a field is only published when it is meant.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from datetime import date, datetime
|
|
11
|
+
from typing import Literal
|
|
12
|
+
|
|
13
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
14
|
+
|
|
15
|
+
#: The shell permission levels, as the SDK names them.
|
|
16
|
+
PermissionName = Literal["deny", "ask", "safe", "auto"]
|
|
17
|
+
|
|
18
|
+
#: How a run answers the shell commands the model asks to run.
|
|
19
|
+
ApprovalMode = Literal["deny", "auto", "commands", "ask"]
|
|
20
|
+
|
|
21
|
+
#: Whether a question the model asks waits for the client.
|
|
22
|
+
QuestionMode = Literal["ask", "decline"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ApprovalPolicy(BaseModel):
|
|
26
|
+
"""The rule a run answers shell approvals with.
|
|
27
|
+
|
|
28
|
+
A run over HTTP is unattended unless the client stays in the conversation: the agent is suspended inside the shell
|
|
29
|
+
tool until the request is answered. ``deny`` refuses every command (the default), ``auto`` approves every one, and
|
|
30
|
+
``commands`` approves only the commands named and refuses the rest. ``ask`` leaves each one to the client: it
|
|
31
|
+
arrives in the stream as ``approval_requested`` and is answered with ``POST /runs/approvals``, or denied once
|
|
32
|
+
``answer_timeout`` runs out.
|
|
33
|
+
|
|
34
|
+
Attributes:
|
|
35
|
+
mode: Which rule applies.
|
|
36
|
+
commands: The commands ``commands`` approves, such as ``"git status"``. A command matches when it is one of
|
|
37
|
+
them or continues one with arguments; one holding a shell character (``;``, ``|``, ``&&``) never matches.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
mode: ApprovalMode = "deny"
|
|
41
|
+
commands: list[str] = Field(default_factory=list)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class RunOptions(BaseModel):
|
|
45
|
+
"""What every run answers with, whether it starts a prompt or continues an interrupted one.
|
|
46
|
+
|
|
47
|
+
Attributes:
|
|
48
|
+
approvals: How shell commands the model asks to run are answered.
|
|
49
|
+
questions: ``ask`` sends a question the model asks down the stream and waits for
|
|
50
|
+
``POST /runs/questions``; ``decline`` tells the model the reader is not there.
|
|
51
|
+
timeout: Seconds the whole run may take before it is stopped, or ``None`` for no deadline.
|
|
52
|
+
answer_timeout: Seconds an ``ask`` approval or question waits for the client before the safe default is
|
|
53
|
+
applied (deny, decline), so a client that went away cannot hang the run.
|
|
54
|
+
keep_events: Include every event in the final result as well, as the SDK's ``keep_events`` does.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
approvals: ApprovalPolicy = Field(default_factory=ApprovalPolicy)
|
|
58
|
+
questions: QuestionMode = "decline"
|
|
59
|
+
timeout: float | None = Field(default=None, gt=0)
|
|
60
|
+
answer_timeout: float = Field(default=300.0, gt=0, le=3600)
|
|
61
|
+
keep_events: bool = False
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class ImageIn(BaseModel):
|
|
65
|
+
"""An image sent with a prompt, base64 encoded.
|
|
66
|
+
|
|
67
|
+
Attributes:
|
|
68
|
+
data: The file's bytes, base64 encoded (no data-URL prefix).
|
|
69
|
+
media_type: ``image/png``, ``image/jpeg``, ``image/gif`` or ``image/webp``.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
data: str = Field(min_length=1)
|
|
73
|
+
media_type: str = Field(min_length=1)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class PromptRequest(RunOptions):
|
|
77
|
+
"""One prompt, streamed as it runs.
|
|
78
|
+
|
|
79
|
+
Attributes:
|
|
80
|
+
text: What to ask the agent; ``@path`` references are resolved when ``mentions`` is on.
|
|
81
|
+
images: Pictures for a model that takes them.
|
|
82
|
+
mentions: Resolve ``@`` references in the text, as the terminal does.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
text: str = Field(min_length=1)
|
|
86
|
+
images: list[ImageIn] = Field(default_factory=list)
|
|
87
|
+
mentions: bool = True
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class ResumeRequest(RunOptions):
|
|
91
|
+
"""Continue the request that was interrupted; nothing is asked, so only the answers are named."""
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class HealthOut(BaseModel):
|
|
95
|
+
"""What ``/health`` answers: the process is up, and what it has to work with."""
|
|
96
|
+
|
|
97
|
+
status: Literal["ok"] = "ok"
|
|
98
|
+
version: str
|
|
99
|
+
workspace: str
|
|
100
|
+
#: Whether a model is configured, so a prompt can run. A fresh machine is ``false`` until ``POST /models``.
|
|
101
|
+
ready: bool = False
|
|
102
|
+
session: str | None = None
|
|
103
|
+
models: int = 0
|
|
104
|
+
busy: bool
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class UsageOut(BaseModel):
|
|
108
|
+
"""Token counts, for the session or for one run."""
|
|
109
|
+
|
|
110
|
+
input_tokens: int = 0
|
|
111
|
+
output_tokens: int = 0
|
|
112
|
+
cache_read_tokens: int = 0
|
|
113
|
+
cache_write_tokens: int = 0
|
|
114
|
+
reasoning_tokens: int = 0
|
|
115
|
+
seconds: float = 0.0
|
|
116
|
+
requests: int = 0
|
|
117
|
+
context_tokens: int = 0
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def total_tokens(self) -> int:
|
|
121
|
+
"""Return input plus output, as the SDK's ``Usage.total_tokens``."""
|
|
122
|
+
return self.input_tokens + self.output_tokens
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class ModelOut(BaseModel):
|
|
126
|
+
"""One configured model."""
|
|
127
|
+
|
|
128
|
+
name: str
|
|
129
|
+
model: str
|
|
130
|
+
context_window: int
|
|
131
|
+
multimodal: bool
|
|
132
|
+
efforts: list[str]
|
|
133
|
+
active: bool = False
|
|
134
|
+
compact_percent: float = 80.0
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class ModelsOut(BaseModel):
|
|
138
|
+
"""Every configured model, and which one a run would use."""
|
|
139
|
+
|
|
140
|
+
models: list[ModelOut]
|
|
141
|
+
current: str | None = None
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class SettingsOut(BaseModel):
|
|
145
|
+
"""The model, reasoning effort and permission a run starts with."""
|
|
146
|
+
|
|
147
|
+
model: ModelOut
|
|
148
|
+
effort: str
|
|
149
|
+
efforts: list[str]
|
|
150
|
+
permission: PermissionName
|
|
151
|
+
permissions: list[str]
|
|
152
|
+
unsaved: str | None = None
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class SettingsUpdate(BaseModel):
|
|
156
|
+
"""A change to what a run starts with; every field left out stays as it is."""
|
|
157
|
+
|
|
158
|
+
model: str | None = None
|
|
159
|
+
effort: str | None = None
|
|
160
|
+
permission: PermissionName | None = None
|
|
161
|
+
compact_percent: float | None = Field(default=None, gt=0, le=100)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class ModelCreate(BaseModel):
|
|
165
|
+
"""One model to write into the configuration file.
|
|
166
|
+
|
|
167
|
+
The keys are the file's own, so what is added here is what ``/config add`` writes and what the terminal reads.
|
|
168
|
+
A token may be left out when the environment names one (``OPENAI_API_KEY``, ``ANTHROPIC_API_KEY``).
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
model_config = ConfigDict(extra="forbid")
|
|
172
|
+
|
|
173
|
+
model: str = Field(min_length=1)
|
|
174
|
+
token: str | None = None
|
|
175
|
+
display_model: str | None = None
|
|
176
|
+
base_url: str | None = None
|
|
177
|
+
anthropic: bool = False
|
|
178
|
+
responses_api: bool = False
|
|
179
|
+
multimodal: bool = False
|
|
180
|
+
context_window: int | None = Field(default=None, ge=1)
|
|
181
|
+
compact_percent: float | None = Field(default=None, gt=0, le=100)
|
|
182
|
+
reasoning_efforts: list[str] = Field(default_factory=list)
|
|
183
|
+
|
|
184
|
+
def entry(self) -> dict[str, object]:
|
|
185
|
+
"""Return the file keys to write, leaving out what was not given."""
|
|
186
|
+
data = self.model_dump(exclude_none=True)
|
|
187
|
+
for flag in ("anthropic", "responses_api", "multimodal"):
|
|
188
|
+
if not data.get(flag):
|
|
189
|
+
data.pop(flag, None)
|
|
190
|
+
if not data.get("reasoning_efforts"):
|
|
191
|
+
data.pop("reasoning_efforts", None)
|
|
192
|
+
return data
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class ModelUpdate(BaseModel):
|
|
196
|
+
"""Changes to one configured model; a key left out stays as it is, and the token is kept."""
|
|
197
|
+
|
|
198
|
+
model_config = ConfigDict(extra="forbid")
|
|
199
|
+
|
|
200
|
+
model: str | None = Field(default=None, min_length=1)
|
|
201
|
+
token: str | None = None
|
|
202
|
+
display_model: str | None = None
|
|
203
|
+
base_url: str | None = None
|
|
204
|
+
anthropic: bool | None = None
|
|
205
|
+
responses_api: bool | None = None
|
|
206
|
+
multimodal: bool | None = None
|
|
207
|
+
context_window: int | None = Field(default=None, ge=1)
|
|
208
|
+
compact_percent: float | None = Field(default=None, gt=0, le=100)
|
|
209
|
+
reasoning_efforts: list[str] | None = None
|
|
210
|
+
|
|
211
|
+
def changes(self) -> dict[str, object]:
|
|
212
|
+
"""Return only the keys the request set, as the file writes them."""
|
|
213
|
+
return self.model_dump(exclude_unset=True, exclude_none=True)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class SessionOut(BaseModel):
|
|
217
|
+
"""One stored session, as a list shows it."""
|
|
218
|
+
|
|
219
|
+
id: str
|
|
220
|
+
title: str | None = None
|
|
221
|
+
created_at: datetime | None = None
|
|
222
|
+
updated_at: datetime | None = None
|
|
223
|
+
current: bool = False
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class SessionsOut(BaseModel):
|
|
227
|
+
"""The stored sessions, most recently active first."""
|
|
228
|
+
|
|
229
|
+
sessions: list[SessionOut]
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class CurrentSessionOut(BaseModel):
|
|
233
|
+
"""The conversation the next prompt continues."""
|
|
234
|
+
|
|
235
|
+
id: str
|
|
236
|
+
title: str | None = None
|
|
237
|
+
locked: bool = False
|
|
238
|
+
usage: UsageOut
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class SessionCreate(BaseModel):
|
|
242
|
+
"""The body of a new session: a name for it, or none until one is written."""
|
|
243
|
+
|
|
244
|
+
title: str | None = Field(default=None, max_length=500)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class RenameRequest(BaseModel):
|
|
248
|
+
"""A new name for a session."""
|
|
249
|
+
|
|
250
|
+
title: str = Field(min_length=1, max_length=500)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class ToolOut(BaseModel):
|
|
254
|
+
"""One tool call and how it ended."""
|
|
255
|
+
|
|
256
|
+
call_id: str
|
|
257
|
+
name: str
|
|
258
|
+
arguments: dict[str, object] = Field(default_factory=dict)
|
|
259
|
+
output: str = ""
|
|
260
|
+
ok: bool = True
|
|
261
|
+
skipped: bool = False
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class MessageOut(BaseModel):
|
|
265
|
+
"""One message of a stored conversation."""
|
|
266
|
+
|
|
267
|
+
role: str
|
|
268
|
+
text: str
|
|
269
|
+
tool_calls: list[ToolOut] = Field(default_factory=list)
|
|
270
|
+
tool_call_id: str | None = None
|
|
271
|
+
tool_name: str | None = None
|
|
272
|
+
ok: bool = True
|
|
273
|
+
images: int = 0
|
|
274
|
+
usage: UsageOut | None = None
|
|
275
|
+
seconds: float = 0.0
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
class HistoryOut(BaseModel):
|
|
279
|
+
"""A stored conversation, oldest message first."""
|
|
280
|
+
|
|
281
|
+
messages: list[MessageOut]
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class CompactionOut(BaseModel):
|
|
285
|
+
"""A compaction: how much was folded into the summary, and how much was kept."""
|
|
286
|
+
|
|
287
|
+
applied: bool
|
|
288
|
+
summary: str = ""
|
|
289
|
+
compressed: int = 0
|
|
290
|
+
kept: int = 0
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class ContextShareOut(BaseModel):
|
|
294
|
+
"""One source filling the context window."""
|
|
295
|
+
|
|
296
|
+
name: str
|
|
297
|
+
tokens: int
|
|
298
|
+
items: int = 0
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
class ContextOut(BaseModel):
|
|
302
|
+
"""How full the context window is, and what is filling it."""
|
|
303
|
+
|
|
304
|
+
window: int
|
|
305
|
+
used: int
|
|
306
|
+
measured_by: str
|
|
307
|
+
shares: list[ContextShareOut] = Field(default_factory=list)
|
|
308
|
+
partial: bool = False
|
|
309
|
+
from_provider: bool = False
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
class ResultOut(BaseModel):
|
|
313
|
+
"""What a finished run came to."""
|
|
314
|
+
|
|
315
|
+
text: str
|
|
316
|
+
session_id: str
|
|
317
|
+
usage: UsageOut
|
|
318
|
+
tools: list[ToolOut] = Field(default_factory=list)
|
|
319
|
+
stop: str | None = None
|
|
320
|
+
stop_message: str | None = None
|
|
321
|
+
questions: list[str] = Field(default_factory=list)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
class CancelledOut(BaseModel):
|
|
325
|
+
"""Whether there was a run to stop."""
|
|
326
|
+
|
|
327
|
+
cancelled: bool
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
class ApprovalAnswer(BaseModel):
|
|
331
|
+
"""The client's answer to one shell approval the run is waiting on.
|
|
332
|
+
|
|
333
|
+
Attributes:
|
|
334
|
+
call_id: The ``call_id`` of the ``approval_requested`` frame.
|
|
335
|
+
approve: Run the command, or refuse it.
|
|
336
|
+
remember: Let the same kind of command run unasked for the rest of the run.
|
|
337
|
+
"""
|
|
338
|
+
|
|
339
|
+
call_id: str = Field(min_length=1)
|
|
340
|
+
approve: bool = Field(strict=True)
|
|
341
|
+
remember: bool = Field(strict=True, default=False)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
class QuestionAnswer(BaseModel):
|
|
345
|
+
"""The client's answer to one question the model asked.
|
|
346
|
+
|
|
347
|
+
Attributes:
|
|
348
|
+
call_id: The ``call_id`` of the ``question_asked`` frame.
|
|
349
|
+
answer: The text to send the model; ``null`` declines the question.
|
|
350
|
+
"""
|
|
351
|
+
|
|
352
|
+
call_id: str = Field(min_length=1)
|
|
353
|
+
answer: str | None = None
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
class SteerIn(BaseModel):
|
|
357
|
+
"""A message for the run in progress."""
|
|
358
|
+
|
|
359
|
+
text: str = Field(min_length=1)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
class SteeredOut(BaseModel):
|
|
363
|
+
"""Whether a running request took the message; ``false`` when nothing was running."""
|
|
364
|
+
|
|
365
|
+
steered: bool
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
class PendingOut(BaseModel):
|
|
369
|
+
"""The approval or question a run is waiting on, so a reconnecting client can answer it."""
|
|
370
|
+
|
|
371
|
+
kind: Literal["approval", "question"]
|
|
372
|
+
call_id: str
|
|
373
|
+
command: str | None = None
|
|
374
|
+
timeout_seconds: int | None = None
|
|
375
|
+
remember_supported: bool = False
|
|
376
|
+
question: str | None = None
|
|
377
|
+
options: list[str] = Field(default_factory=list)
|
|
378
|
+
allow_multiple: bool = False
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
class RunStateOut(BaseModel):
|
|
382
|
+
"""Whether a run is going, and what it waits on if it is."""
|
|
383
|
+
|
|
384
|
+
running: bool
|
|
385
|
+
pending: PendingOut | None = None
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
class ResolvedOut(BaseModel):
|
|
389
|
+
"""Whether an answer reached the request it named."""
|
|
390
|
+
|
|
391
|
+
resolved: bool
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
class ResumableOut(BaseModel):
|
|
395
|
+
"""Where the conversation was interrupted, and what continuing would do."""
|
|
396
|
+
|
|
397
|
+
resumable: bool
|
|
398
|
+
kind: str | None = None
|
|
399
|
+
description: str | None = None
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
class SideQuestionRequest(BaseModel):
|
|
403
|
+
"""A question answered on the side: it never joins the conversation.
|
|
404
|
+
|
|
405
|
+
Attributes:
|
|
406
|
+
question: What to ask.
|
|
407
|
+
timeout: Seconds to wait for the answer, or ``None`` for no deadline.
|
|
408
|
+
"""
|
|
409
|
+
|
|
410
|
+
question: str = Field(min_length=1)
|
|
411
|
+
timeout: float | None = Field(default=None, gt=0)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
class AnswerOut(BaseModel):
|
|
415
|
+
"""The answer to a side question."""
|
|
416
|
+
|
|
417
|
+
answer: str
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
class PluginsOut(BaseModel):
|
|
421
|
+
"""The plugins this server loaded, and the ones that refused to load."""
|
|
422
|
+
|
|
423
|
+
loaded: list[str]
|
|
424
|
+
failures: list[str]
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
class MentionOut(BaseModel):
|
|
428
|
+
"""One ``@`` resource a prompt can carry, as a completion menu lists it."""
|
|
429
|
+
|
|
430
|
+
token: str
|
|
431
|
+
kind: str
|
|
432
|
+
name: str
|
|
433
|
+
description: str
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
class MentionsOut(BaseModel):
|
|
437
|
+
"""The ``@`` resources matching what was typed."""
|
|
438
|
+
|
|
439
|
+
mentions: list[MentionOut]
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
class TotalsOut(BaseModel):
|
|
443
|
+
"""Token counters added up over some replies, as the usage ledger records them."""
|
|
444
|
+
|
|
445
|
+
requests: int = 0
|
|
446
|
+
input_tokens: int = 0
|
|
447
|
+
output_tokens: int = 0
|
|
448
|
+
cache_read_tokens: int = 0
|
|
449
|
+
reasoning_tokens: int = 0
|
|
450
|
+
total_tokens: int = 0
|
|
451
|
+
cache_share: float | None = None
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
class SessionTotalsOut(BaseModel):
|
|
455
|
+
"""One session in the workspace's usage."""
|
|
456
|
+
|
|
457
|
+
id: str
|
|
458
|
+
title: str | None = None
|
|
459
|
+
started: datetime
|
|
460
|
+
updated: datetime
|
|
461
|
+
tokens: TotalsOut
|
|
462
|
+
models: list[str] = Field(default_factory=list)
|
|
463
|
+
entries: int = 0
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
class ModelTotalsOut(BaseModel):
|
|
467
|
+
"""What one model was charged across the workspace."""
|
|
468
|
+
|
|
469
|
+
model: str
|
|
470
|
+
tokens: TotalsOut
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
class DayTotalsOut(BaseModel):
|
|
474
|
+
"""What one day spent across the workspace."""
|
|
475
|
+
|
|
476
|
+
day: date
|
|
477
|
+
tokens: TotalsOut
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
class WorkspaceUsageOut(BaseModel):
|
|
481
|
+
"""What the workspace's stored sessions have spent, as the terminal's ``/usage`` page shows it."""
|
|
482
|
+
|
|
483
|
+
sessions: list[SessionTotalsOut]
|
|
484
|
+
models: list[ModelTotalsOut]
|
|
485
|
+
days: list[DayTotalsOut]
|
|
486
|
+
total: TotalsOut
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
class SessionStatsOut(BaseModel):
|
|
490
|
+
"""The totals one row of the terminal's ``/resume`` panel shows."""
|
|
491
|
+
|
|
492
|
+
id: str
|
|
493
|
+
entries: int = 0
|
|
494
|
+
requests: int = 0
|
|
495
|
+
input_tokens: int = 0
|
|
496
|
+
output_tokens: int = 0
|
|
497
|
+
cache_read_tokens: int = 0
|
|
498
|
+
reasoning_tokens: int = 0
|
|
499
|
+
total_tokens: int = 0
|
|
500
|
+
cache_share: float | None = None
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
class SessionStatsListOut(BaseModel):
|
|
504
|
+
"""The stored totals of the sessions asked for."""
|
|
505
|
+
|
|
506
|
+
stats: list[SessionStatsOut]
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
class SelfCompactionOut(BaseModel):
|
|
510
|
+
"""Whether the model may compact this session itself, and the session's own choice."""
|
|
511
|
+
|
|
512
|
+
choice: bool | None = None
|
|
513
|
+
effective: bool
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
class SelfCompactionUpdate(BaseModel):
|
|
517
|
+
"""A choice about self-compaction: ``true``, ``false``, or ``null`` to follow the configuration."""
|
|
518
|
+
|
|
519
|
+
choice: bool | None = Field(default=None, strict=True)
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
class ModelCheckOut(BaseModel):
|
|
523
|
+
"""One model asked one short question by ``/doctor``."""
|
|
524
|
+
|
|
525
|
+
model: str
|
|
526
|
+
model_id: str
|
|
527
|
+
ok: bool
|
|
528
|
+
seconds: float
|
|
529
|
+
detail: str = ""
|
|
530
|
+
input_tokens: int | None = None
|
|
531
|
+
output_tokens: int | None = None
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
class DoctorOut(BaseModel):
|
|
535
|
+
"""Every configured model, and whether it answers."""
|
|
536
|
+
|
|
537
|
+
checks: list[ModelCheckOut]
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
class DoctorRequest(BaseModel):
|
|
541
|
+
"""How long each model may take to answer."""
|
|
542
|
+
|
|
543
|
+
timeout: float = Field(default=30.0, gt=0, le=300)
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
class ErrorOut(BaseModel):
|
|
547
|
+
"""A failure, in a shape a client can branch on.
|
|
548
|
+
|
|
549
|
+
Attributes:
|
|
550
|
+
error: A stable code: ``not_found``, ``busy``, ``locked``, ``invalid``, ``timeout``, ``run``, ``internal``.
|
|
551
|
+
message: The sentence the SDK gave, which is the one the terminal would show.
|
|
552
|
+
"""
|
|
553
|
+
|
|
554
|
+
error: str
|
|
555
|
+
message: str
|