bazaar-contrib-server-activity 0.3.0rc1__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.
Files changed (48) hide show
  1. bazaar_contrib_server_activity/__init__.py +208 -0
  2. bazaar_contrib_server_activity/contracts.py +155 -0
  3. bazaar_contrib_server_activity/ledger.py +646 -0
  4. bazaar_contrib_server_activity/pages.py +580 -0
  5. bazaar_contrib_server_activity/py.typed +0 -0
  6. bazaar_contrib_server_activity/resources/locales/en.toml +91 -0
  7. bazaar_contrib_server_activity/resources/locales/zh-CN.toml +91 -0
  8. bazaar_contrib_server_activity/resources/static/activity.css +193 -0
  9. bazaar_contrib_server_activity/resources/static/activity.js +39 -0
  10. bazaar_contrib_server_activity/resources/templates/activity/feed.html +40 -0
  11. bazaar_contrib_server_activity/resources/templates/activity/icons/activity.svg +1 -0
  12. bazaar_contrib_server_activity/resources/templates/activity/icons/alarm-clock.svg +1 -0
  13. bazaar_contrib_server_activity/resources/templates/activity/icons/check.svg +1 -0
  14. bazaar_contrib_server_activity/resources/templates/activity/icons/chevron-left.svg +1 -0
  15. bazaar_contrib_server_activity/resources/templates/activity/icons/chevron-right.svg +1 -0
  16. bazaar_contrib_server_activity/resources/templates/activity/icons/circle-check.svg +1 -0
  17. bazaar_contrib_server_activity/resources/templates/activity/icons/clock.svg +1 -0
  18. bazaar_contrib_server_activity/resources/templates/activity/icons/puzzle.svg +1 -0
  19. bazaar_contrib_server_activity/resources/templates/activity/icons/send.svg +1 -0
  20. bazaar_contrib_server_activity/resources/templates/activity/icons/sparkles.svg +1 -0
  21. bazaar_contrib_server_activity/resources/templates/activity/icons/terminal.svg +1 -0
  22. bazaar_contrib_server_activity/resources/templates/activity/icons/triangle-alert.svg +1 -0
  23. bazaar_contrib_server_activity/resources/templates/activity/icons/user-plus.svg +1 -0
  24. bazaar_contrib_server_activity/resources/templates/activity/icons/users.svg +1 -0
  25. bazaar_contrib_server_activity/resources/templates/activity/icons/wifi-off.svg +1 -0
  26. bazaar_contrib_server_activity/resources/templates/activity/icons/zap.svg +1 -0
  27. bazaar_contrib_server_activity/resources/templates/activity/live.html +52 -0
  28. bazaar_contrib_server_activity/resources/templates/activity/log.html +19 -0
  29. bazaar_contrib_server_activity/resources/templates/activity/logos/THIRD_PARTY.md +9 -0
  30. bazaar_contrib_server_activity/resources/templates/activity/logos/claudecode.svg +1 -0
  31. bazaar_contrib_server_activity/resources/templates/activity/logos/codex.svg +1 -0
  32. bazaar_contrib_server_activity/resources/templates/activity/logos/lark.svg +1 -0
  33. bazaar_contrib_server_activity/resources/templates/activity/logos/telegram.svg +1 -0
  34. bazaar_contrib_server_activity/resources/templates/activity/logos/wecom.svg +1 -0
  35. bazaar_contrib_server_activity/resources/templates/activity/macros.html +71 -0
  36. bazaar_contrib_server_activity/resources/templates/activity/output.html +1 -0
  37. bazaar_contrib_server_activity/resources/templates/activity/rows.html +76 -0
  38. bazaar_contrib_server_activity/resources/templates/activity/stall.html +41 -0
  39. bazaar_contrib_server_activity/resources/templates/activity/today.html +34 -0
  40. bazaar_contrib_server_activity/resources/templates/activity/usage.html +22 -0
  41. bazaar_contrib_server_activity/schema.py +399 -0
  42. bazaar_contrib_server_activity/service.py +181 -0
  43. bazaar_contrib_server_activity/store.py +930 -0
  44. bazaar_contrib_server_activity-0.3.0rc1.dist-info/METADATA +40 -0
  45. bazaar_contrib_server_activity-0.3.0rc1.dist-info/RECORD +48 -0
  46. bazaar_contrib_server_activity-0.3.0rc1.dist-info/WHEEL +4 -0
  47. bazaar_contrib_server_activity-0.3.0rc1.dist-info/entry_points.txt +3 -0
  48. bazaar_contrib_server_activity-0.3.0rc1.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,208 @@
1
+ """The activity of the Bazaar Compute server: what the computers report,
2
+ kept once and cleared after the retention, the turns, usage and departures
3
+ it adds up to, ACTIVITY for the modules that read them, and the activity's
4
+ pages - the sections and the whole record of an agent, the agents in each
5
+ stall and the market's feed."""
6
+
7
+ from __future__ import annotations
8
+
9
+ from contextlib import asynccontextmanager
10
+ lazy import asyncio
11
+ lazy from collections.abc import AsyncIterator
12
+ lazy from importlib.resources import files
13
+ lazy from pathlib import Path
14
+
15
+ from bazaar_contrib_server import SERVER, Topic
16
+ from bazaar_contrib_server_access import ACCESS
17
+ from bazaar_contrib_server_agent import AGENTS, AgentSection, AgentTab
18
+ from bazaar_contrib_server_computer import (
19
+ COMPUTER_LEFT,
20
+ COMPUTER_REMOVED,
21
+ COMPUTERS,
22
+ REPORTED,
23
+ ComputerLeft,
24
+ ComputerRemoved,
25
+ Reported,
26
+ )
27
+ from bazaar_contrib_server_database import SERVER_DB, SERVER_SCHEMA
28
+ from bazaar_contrib_server_migrations import SERVER_MIGRATIONS
29
+ from bazaar_contrib_server_overview import OVERVIEW, OverviewItem
30
+ from bazaar_contrib_timer import TIMER
31
+ from bazaar_core import Context, Contrib, ServiceKey
32
+ from pydantic import BaseModel, ConfigDict, Field
33
+
34
+ from .contracts import ACTIVITY, Activity, Bucket, Calls, Receipt, Turn
35
+ lazy from .ledger import Ledger
36
+ lazy from .pages import VIEW, Views
37
+ lazy from .schema import MODULE
38
+ lazy from .service import Service
39
+ lazy from .store import Store
40
+
41
+ __all__ = [
42
+ "ACTIVITY",
43
+ "Activity",
44
+ "ActivityConfig",
45
+ "Bucket",
46
+ "Calls",
47
+ "Receipt",
48
+ "Turn",
49
+ "activity",
50
+ ]
51
+
52
+ # what the computers report, kept as it comes
53
+ _LEDGER = ServiceKey["Ledger"]("server.activity.ledger")
54
+ # the retention is swept once a day, as 0.2 did
55
+ DAY_MS = 86_400_000
56
+
57
+
58
+ class ActivityConfig(BaseModel):
59
+ model_config = ConfigDict(extra="forbid", frozen=True)
60
+
61
+ # how many days of what the computers report are kept
62
+ retention_days: int = Field(default=30, ge=1, strict=True)
63
+
64
+
65
+ @asynccontextmanager
66
+ async def _activity(ctx: Context, config: ActivityConfig) -> AsyncIterator[None]:
67
+ server = ctx.accept(SERVER)
68
+ agents = ctx.accept(AGENTS)
69
+ timer = ctx.accept(TIMER)
70
+ database = ctx.accept(SERVER_DB)
71
+ resources = Path(str(files(__name__))) / "resources"
72
+ icon = await asyncio.to_thread(
73
+ (resources / "templates" / "activity" / "icons" / "activity.svg").read_text,
74
+ encoding="utf-8",
75
+ )
76
+ async with ctx.accept(SERVER_SCHEMA).register(MODULE):
77
+ ledger = Ledger(database)
78
+ store = Store(database)
79
+ pages = await server.pages("activity", __name__)
80
+ views = Views(
81
+ server,
82
+ pages,
83
+ ctx.accept(ACCESS),
84
+ agents,
85
+ ctx.accept(COMPUTERS),
86
+ store,
87
+ )
88
+
89
+ async def sweep() -> None:
90
+ while True:
91
+ await ledger.sweep(config.retention_days)
92
+ await timer.create(DAY_MS).wait()
93
+
94
+ async with (
95
+ asyncio.TaskGroup() as tasks,
96
+ server.module(
97
+ "activity",
98
+ ui=pages.application(views.routes),
99
+ static=resources / "static",
100
+ locales=resources / "locales",
101
+ ),
102
+ server.topic(
103
+ Topic(
104
+ name="activity.agent",
105
+ fetch=views.followed,
106
+ fields=("agent",),
107
+ permission=VIEW,
108
+ )
109
+ ),
110
+ server.topic(Topic(name="activity.feed", fetch=views.fed)),
111
+ server.topic(
112
+ Topic(
113
+ name="activity.stall",
114
+ fetch=views.stalled,
115
+ fields=("computer",),
116
+ permission=VIEW,
117
+ )
118
+ ),
119
+ agents.section(
120
+ AgentSection(
121
+ key="live",
122
+ src="/ui/activity/live",
123
+ column="main",
124
+ permission=VIEW,
125
+ order=10,
126
+ )
127
+ ),
128
+ agents.section(
129
+ AgentSection(
130
+ key="today",
131
+ src="/ui/activity/today",
132
+ column="main",
133
+ permission=VIEW,
134
+ order=20,
135
+ )
136
+ ),
137
+ agents.section(
138
+ AgentSection(
139
+ key="usage",
140
+ src="/ui/activity/usage",
141
+ column="side",
142
+ permission=VIEW,
143
+ order=20,
144
+ )
145
+ ),
146
+ # the whole record, opened from the activity and lighting it
147
+ agents.tab(
148
+ AgentTab(
149
+ key="log",
150
+ text="activity.log.title",
151
+ icon=icon,
152
+ src="/ui/activity/log",
153
+ permission=VIEW,
154
+ parent="activity",
155
+ )
156
+ ),
157
+ ctx.accept(COMPUTERS).stall("/ui/activity/stall"),
158
+ ctx.accept(OVERVIEW).register(
159
+ OverviewItem(
160
+ key="feed",
161
+ title="activity.feed.title",
162
+ src="/ui/activity/feed",
163
+ order=40,
164
+ )
165
+ ),
166
+ ):
167
+ task = tasks.create_task(sweep())
168
+ ctx.offer(_LEDGER, ledger)
169
+ ctx.offer(ACTIVITY, Service(store, agents))
170
+ try:
171
+ yield
172
+ finally:
173
+ task.cancel()
174
+
175
+
176
+ async def _reported(ctx: Context, value: Reported) -> None:
177
+ await ctx.accept(_LEDGER).record(value.computer_id, value.event)
178
+
179
+
180
+ async def _removed(ctx: Context, value: ComputerRemoved) -> None:
181
+ await ctx.accept(_LEDGER).remove(value.computer_id)
182
+
183
+
184
+ async def _left(ctx: Context, value: ComputerLeft) -> None:
185
+ await ctx.accept(_LEDGER).depart(value.computer_id, value.seen_ms)
186
+
187
+
188
+ activity = Contrib(
189
+ config=ActivityConfig,
190
+ accepts=[
191
+ SERVER,
192
+ ACCESS,
193
+ OVERVIEW,
194
+ COMPUTERS,
195
+ AGENTS,
196
+ SERVER_DB,
197
+ SERVER_SCHEMA,
198
+ SERVER_MIGRATIONS,
199
+ TIMER,
200
+ ],
201
+ offers=[_LEDGER, ACTIVITY],
202
+ lifespan=_activity,
203
+ listen={
204
+ REPORTED: _reported,
205
+ COMPUTER_REMOVED: _removed,
206
+ COMPUTER_LEFT: _left,
207
+ },
208
+ )
@@ -0,0 +1,155 @@
1
+ """What other modules hold server-activity to: the turns, receipts, counts
2
+ and cursors of the agents an account may see, as the events the computers
3
+ report add up to."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from dataclasses import dataclass
8
+ from typing import Literal, Protocol
9
+ lazy from collections.abc import Mapping, Sequence
10
+
11
+ from bazaar_core import ServiceKey
12
+ lazy from bazaar_contrib_server import Account
13
+
14
+
15
+ @dataclass(frozen=True, slots=True)
16
+ class Calls:
17
+ """Tool calls by the kind a receipt words them in: commands run, files
18
+ looked at, files changed, pages opened, web searches, other tools and
19
+ subtasks sent off."""
20
+
21
+ commands: int = 0
22
+ files: int = 0
23
+ changes: int = 0
24
+ pages: int = 0
25
+ searches: int = 0
26
+ tools: int = 0
27
+ tasks: int = 0
28
+
29
+ @property
30
+ def total(self) -> int:
31
+ return (
32
+ self.commands
33
+ + self.files
34
+ + self.changes
35
+ + self.pages
36
+ + self.searches
37
+ + self.tools
38
+ + self.tasks
39
+ )
40
+
41
+
42
+ @dataclass(frozen=True, slots=True)
43
+ class Turn:
44
+ """A turn as its events add up to: where it came from, the runtime and
45
+ model it ran on, how it ended and why, the tokens it used - fresh
46
+ input, cache hits and output - with their cost when the runtime gives
47
+ one, its tool calls, and the step it is at while it runs."""
48
+
49
+ computer_id: str
50
+ agent_id: str
51
+ turn_id: str
52
+ session_id: str | None
53
+ thread_id: str | None
54
+ channel: str | None
55
+ target_kind: str | None
56
+ connection: str | None
57
+ model: str | None
58
+ message_id: str | None
59
+ started_ms: int
60
+ ended_ms: int | None
61
+ outcome: Literal["completed", "failed", "cancelled"] | None
62
+ reason: str | None
63
+ input: int
64
+ cached: int
65
+ output: int
66
+ cost: float | None
67
+ calls: Calls
68
+ step: Literal["reply", "thinking", "tool"] | None
69
+ step_ms: int | None
70
+
71
+
72
+ @dataclass(frozen=True, slots=True)
73
+ class Receipt:
74
+ """What a turn did before one of its replies, since the reply before
75
+ it or its start: its tool calls and how many seconds that took."""
76
+
77
+ calls: Calls
78
+ seconds: int
79
+
80
+
81
+ @dataclass(frozen=True, slots=True)
82
+ class Bucket:
83
+ """The turns that started and the tokens used from a moment until the
84
+ next bucket's."""
85
+
86
+ start_ms: int
87
+ turns: int
88
+ input: int
89
+ cached: int
90
+ output: int
91
+ cost: float | None
92
+
93
+
94
+ class Activity(Protocol):
95
+ """The activity of the agents an account may see; any other agent is
96
+ not there."""
97
+
98
+ async def cursor(
99
+ self,
100
+ account: Account,
101
+ *,
102
+ agent_id: str | None = None,
103
+ thread_id: str | None = None,
104
+ messages: bool = False,
105
+ ) -> int:
106
+ """The id of the newest event of the agent, of every agent the
107
+ account may see for None, in the conversation when one is given;
108
+ only of the messages sent, received and let in when asked: 0 for
109
+ none."""
110
+ ...
111
+
112
+ async def working(
113
+ self,
114
+ account: Account,
115
+ *,
116
+ agent_id: str | None = None,
117
+ thread_id: str | None = None,
118
+ ) -> Sequence[Turn]:
119
+ """The turns in progress on computers that are here, oldest first."""
120
+ ...
121
+
122
+ async def turns(
123
+ self,
124
+ account: Account,
125
+ since_ms: int,
126
+ until_ms: int,
127
+ *,
128
+ agent_id: str | None = None,
129
+ thread_id: str | None = None,
130
+ ) -> Sequence[Turn]:
131
+ """The turns started in the time, newest first."""
132
+ ...
133
+
134
+ async def receipts(
135
+ self, account: Account, agent_id: str, message_ids: Sequence[str]
136
+ ) -> Mapping[str, Receipt]:
137
+ """What the turn behind each reply of the agent did before it, by
138
+ the reply's message id; a reply sent outside a turn has none."""
139
+ ...
140
+
141
+ async def stats(
142
+ self,
143
+ account: Account,
144
+ starts: Sequence[int],
145
+ until_ms: int,
146
+ *,
147
+ agent_id: str | None = None,
148
+ ) -> Sequence[Bucket]:
149
+ """The turns and tokens of each bucket, an hour or a day as the
150
+ caller cut them on its clock, from each start until the next and
151
+ the last until the end."""
152
+ ...
153
+
154
+
155
+ ACTIVITY = ServiceKey[Activity]("server.activity")