zettcode-server 0.4.1__tar.gz → 0.4.2__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.
- {zettcode_server-0.4.1 → zettcode_server-0.4.2}/.gitignore +5 -0
- {zettcode_server-0.4.1 → zettcode_server-0.4.2}/PKG-INFO +2 -2
- {zettcode_server-0.4.1 → zettcode_server-0.4.2}/pyproject.toml +1 -1
- {zettcode_server-0.4.1 → zettcode_server-0.4.2}/src/zettcode/server/app.py +7 -1
- {zettcode_server-0.4.1 → zettcode_server-0.4.2}/src/zettcode/server/runner.py +32 -13
- {zettcode_server-0.4.1 → zettcode_server-0.4.2}/src/zettcode/server/schemas.py +22 -1
- {zettcode_server-0.4.1 → zettcode_server-0.4.2}/src/zettcode/server/__init__.py +0 -0
- {zettcode_server-0.4.1 → zettcode_server-0.4.2}/src/zettcode/server/cli.py +0 -0
- {zettcode_server-0.4.1 → zettcode_server-0.4.2}/src/zettcode/server/py.typed +0 -0
- {zettcode_server-0.4.1 → zettcode_server-0.4.2}/src/zettcode/server/serialize.py +0 -0
- {zettcode_server-0.4.1 → zettcode_server-0.4.2}/src/zettcode/server/settings.py +0 -0
|
@@ -35,3 +35,8 @@ docs/internal/palettes.html
|
|
|
35
35
|
|
|
36
36
|
# The release order is generated from the pyproject.toml files (`make order`); it is not kept in git.
|
|
37
37
|
packages/order.txt
|
|
38
|
+
|
|
39
|
+
# The desktop and phone apps build the gateway's page into their own folders, and electron-builder writes its
|
|
40
|
+
# installers under release/.
|
|
41
|
+
clients/desktop/renderer/
|
|
42
|
+
clients/desktop/release/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: zettcode-server
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: The HTTP face of ZettCode: a FastAPI server that drives the SDK
|
|
5
5
|
Project-URL: Repository, https://github.com/Chang-LeHung/zettcode
|
|
6
6
|
Author: Chang-LeHung
|
|
@@ -17,7 +17,7 @@ Classifier: Typing :: Typed
|
|
|
17
17
|
Requires-Python: >=3.10
|
|
18
18
|
Requires-Dist: fastapi<1,>=0.115
|
|
19
19
|
Requires-Dist: pydantic<3,>=2
|
|
20
|
-
Requires-Dist: starlette<2,>=0.
|
|
20
|
+
Requires-Dist: starlette<2,>=0.51
|
|
21
21
|
Requires-Dist: uvicorn<1,>=0.35
|
|
22
22
|
Requires-Dist: zettcode-engine
|
|
23
23
|
Requires-Dist: zettcode-sdk
|
|
@@ -84,6 +84,7 @@ from .schemas import (
|
|
|
84
84
|
SideQuestionRequest,
|
|
85
85
|
SteeredOut,
|
|
86
86
|
SteerIn,
|
|
87
|
+
TriggerOut,
|
|
87
88
|
UsageOut,
|
|
88
89
|
WorkspaceUsageOut,
|
|
89
90
|
)
|
|
@@ -619,7 +620,7 @@ def create_app(settings: Settings, *, client_factory: ClientFactory | None = Non
|
|
|
619
620
|
|
|
620
621
|
@protected.get("/plugins", response_model=PluginsOut, tags=["models"])
|
|
621
622
|
async def plugins(runner: RunnerDep) -> PluginsOut:
|
|
622
|
-
"""List the plugins this server loaded,
|
|
623
|
+
"""List the plugins this server loaded, what failed, every one a switch can reach, and their triggers."""
|
|
623
624
|
report = runner.client.plugins
|
|
624
625
|
return PluginsOut(
|
|
625
626
|
loaded=list(report.loaded),
|
|
@@ -634,9 +635,14 @@ def create_app(settings: Settings, *, client_factory: ClientFactory | None = Non
|
|
|
634
635
|
setting=entry.setting,
|
|
635
636
|
switchable=entry.switchable,
|
|
636
637
|
why=entry.why,
|
|
638
|
+
note=entry.note,
|
|
637
639
|
)
|
|
638
640
|
for entry in report.entries
|
|
639
641
|
],
|
|
642
|
+
triggers=[
|
|
643
|
+
TriggerOut(name=trigger.name, scope=trigger.scope, state=trigger.state, detail=trigger.detail)
|
|
644
|
+
for trigger in report.triggers
|
|
645
|
+
],
|
|
640
646
|
)
|
|
641
647
|
|
|
642
648
|
@protected.put("/plugins/{name}", response_model=PluginOut, tags=["models"])
|
|
@@ -27,7 +27,7 @@ from dataclasses import dataclass, field
|
|
|
27
27
|
from typing import TYPE_CHECKING, Literal, cast
|
|
28
28
|
|
|
29
29
|
from zettcode.modes import MODE_STEER
|
|
30
|
-
from zettcode.
|
|
30
|
+
from zettcode.sdk.commands import PlainCommandHost
|
|
31
31
|
from zettcode.sdk.errors import SessionBusyError
|
|
32
32
|
from zettcode.sdk.events import AnyEvent, ApprovalRequested, QuestionAsked, Steered
|
|
33
33
|
from zettcode.sdk.handlers import ApprovalHandler, approve_all, approve_commands, deny_all
|
|
@@ -194,6 +194,34 @@ class NoModelError(RuntimeError):
|
|
|
194
194
|
"""
|
|
195
195
|
|
|
196
196
|
|
|
197
|
+
class _SessionHost(PlainCommandHost):
|
|
198
|
+
"""What the session commands do here: the same things the terminal's own versions do, without a screen.
|
|
199
|
+
|
|
200
|
+
Nothing is caught: a busy or locked session, a blank title and a file that cannot be written keep the status the
|
|
201
|
+
rest of the API gives them, and a command that could do nothing answers with the line the session gave it.
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
def __init__(self, client: Client) -> None:
|
|
205
|
+
self._client = client
|
|
206
|
+
|
|
207
|
+
@property
|
|
208
|
+
def _session(self) -> Session:
|
|
209
|
+
return self._client.session
|
|
210
|
+
|
|
211
|
+
async def compact(self) -> str:
|
|
212
|
+
done = await self._session.compact()
|
|
213
|
+
return "Compacted." if done is not None else "Nothing to compact: the conversation is already short."
|
|
214
|
+
|
|
215
|
+
async def title(self, name: str | None) -> str:
|
|
216
|
+
if name:
|
|
217
|
+
return f"Session named {await self._session.rename(name)!r}."
|
|
218
|
+
current = self._session.title
|
|
219
|
+
return f"This session is called {current!r}." if current else "This session has no name yet: /title <name>."
|
|
220
|
+
|
|
221
|
+
async def export(self, path: str | None) -> str:
|
|
222
|
+
return f"Wrote this session to {await self._session.export(path)}."
|
|
223
|
+
|
|
224
|
+
|
|
197
225
|
class Runner:
|
|
198
226
|
"""The agent, one run at a time, and the events and answers of the run that is going.
|
|
199
227
|
|
|
@@ -364,7 +392,8 @@ class Runner:
|
|
|
364
392
|
command = next((item for item in commands if item.name == wanted), None)
|
|
365
393
|
if command is None:
|
|
366
394
|
raise KeyError(wanted)
|
|
367
|
-
|
|
395
|
+
context = PlainCommandContext(argument=argument, level=self.client.permission, host=_SessionHost(self.client))
|
|
396
|
+
result = command.handler(context)
|
|
368
397
|
if inspect.isawaitable(result):
|
|
369
398
|
result = await result
|
|
370
399
|
if isinstance(result, TurnCommandResult):
|
|
@@ -710,17 +739,7 @@ class Runner:
|
|
|
710
739
|
@staticmethod
|
|
711
740
|
def _steer(slot: Slot, message: IncomingMessage) -> str | None:
|
|
712
741
|
"""Hand a message to the run in progress, framed for the model; the text it will report it by, or ``None``."""
|
|
713
|
-
|
|
714
|
-
sender_id=message.sender_id,
|
|
715
|
-
sender=message.sender,
|
|
716
|
-
text=message.text,
|
|
717
|
-
via=message.via,
|
|
718
|
-
hops=message.hops,
|
|
719
|
-
direct=message.direct,
|
|
720
|
-
mode=message.mode,
|
|
721
|
-
group=message.group,
|
|
722
|
-
)
|
|
723
|
-
return slot.client.session._steer_message(relayed.message())
|
|
742
|
+
return slot.client.session._steer_incoming(message)
|
|
724
743
|
|
|
725
744
|
def _hold_message(self, slot: Slot, message: IncomingMessage) -> None:
|
|
726
745
|
"""Put a message in the chat's inbox, with the id its client answers it by."""
|
|
@@ -532,6 +532,8 @@ class PluginOut(BaseModel):
|
|
|
532
532
|
installed one, which `[plugins] disable` holds.
|
|
533
533
|
switchable: Whether there is a switch to draw; a panel shows the rest without one and says ``why``.
|
|
534
534
|
why: Why it cannot be switched, when it cannot; empty otherwise.
|
|
535
|
+
note: What else a panel should say — a plugin that lost its name to one loaded before it has no switch and is
|
|
536
|
+
not a failure, and this says why.
|
|
535
537
|
"""
|
|
536
538
|
|
|
537
539
|
name: str
|
|
@@ -542,14 +544,33 @@ class PluginOut(BaseModel):
|
|
|
542
544
|
setting: str = ""
|
|
543
545
|
switchable: bool = True
|
|
544
546
|
why: str = ""
|
|
547
|
+
note: str = ""
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
class TriggerOut(BaseModel):
|
|
551
|
+
"""Where one trigger a plugin registered is.
|
|
552
|
+
|
|
553
|
+
Attributes:
|
|
554
|
+
name: The trigger's name.
|
|
555
|
+
scope: ``"process"`` (one per process) or ``"machine"`` (one on the whole machine).
|
|
556
|
+
state: ``pending``, ``standby`` (another process holds the machine's lock), ``running``, ``skipped``
|
|
557
|
+
(another runtime in this process runs it), ``finished``, ``failed`` or ``stopped``.
|
|
558
|
+
detail: Why, for ``standby``, ``skipped`` and ``failed``; empty otherwise.
|
|
559
|
+
"""
|
|
560
|
+
|
|
561
|
+
name: str
|
|
562
|
+
scope: str
|
|
563
|
+
state: str
|
|
564
|
+
detail: str = ""
|
|
545
565
|
|
|
546
566
|
|
|
547
567
|
class PluginsOut(BaseModel):
|
|
548
|
-
"""The plugins this server loaded,
|
|
568
|
+
"""The plugins this server loaded, what refused to load, every one a switch can reach, and its triggers."""
|
|
549
569
|
|
|
550
570
|
loaded: list[str]
|
|
551
571
|
failures: list[str]
|
|
552
572
|
entries: list[PluginOut] = Field(default_factory=list)
|
|
573
|
+
triggers: list[TriggerOut] = Field(default_factory=list)
|
|
553
574
|
|
|
554
575
|
|
|
555
576
|
class PluginSwitchIn(BaseModel):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|