zettcode-server 0.4.0__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.
@@ -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.0
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.48
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
@@ -20,7 +20,7 @@ dependencies = [
20
20
  "zettcode-engine",
21
21
  "zettcode-sdk",
22
22
  "fastapi>=0.115,<1",
23
- "starlette>=0.48,<2",
23
+ "starlette>=0.51,<2",
24
24
  "uvicorn>=0.35,<1",
25
25
  "pydantic>=2,<3",
26
26
  ]
@@ -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, the ones that failed, and every one a switch can reach."""
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.relay import Relayed
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
- result = command.handler(PlainCommandContext(argument=argument, level=self.client.permission))
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
- relayed = Relayed(
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, the ones that refused to load, and every one a switch can reach."""
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):