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.
- bazaar_contrib_server_activity/__init__.py +208 -0
- bazaar_contrib_server_activity/contracts.py +155 -0
- bazaar_contrib_server_activity/ledger.py +646 -0
- bazaar_contrib_server_activity/pages.py +580 -0
- bazaar_contrib_server_activity/py.typed +0 -0
- bazaar_contrib_server_activity/resources/locales/en.toml +91 -0
- bazaar_contrib_server_activity/resources/locales/zh-CN.toml +91 -0
- bazaar_contrib_server_activity/resources/static/activity.css +193 -0
- bazaar_contrib_server_activity/resources/static/activity.js +39 -0
- bazaar_contrib_server_activity/resources/templates/activity/feed.html +40 -0
- bazaar_contrib_server_activity/resources/templates/activity/icons/activity.svg +1 -0
- bazaar_contrib_server_activity/resources/templates/activity/icons/alarm-clock.svg +1 -0
- bazaar_contrib_server_activity/resources/templates/activity/icons/check.svg +1 -0
- bazaar_contrib_server_activity/resources/templates/activity/icons/chevron-left.svg +1 -0
- bazaar_contrib_server_activity/resources/templates/activity/icons/chevron-right.svg +1 -0
- bazaar_contrib_server_activity/resources/templates/activity/icons/circle-check.svg +1 -0
- bazaar_contrib_server_activity/resources/templates/activity/icons/clock.svg +1 -0
- bazaar_contrib_server_activity/resources/templates/activity/icons/puzzle.svg +1 -0
- bazaar_contrib_server_activity/resources/templates/activity/icons/send.svg +1 -0
- bazaar_contrib_server_activity/resources/templates/activity/icons/sparkles.svg +1 -0
- bazaar_contrib_server_activity/resources/templates/activity/icons/terminal.svg +1 -0
- bazaar_contrib_server_activity/resources/templates/activity/icons/triangle-alert.svg +1 -0
- bazaar_contrib_server_activity/resources/templates/activity/icons/user-plus.svg +1 -0
- bazaar_contrib_server_activity/resources/templates/activity/icons/users.svg +1 -0
- bazaar_contrib_server_activity/resources/templates/activity/icons/wifi-off.svg +1 -0
- bazaar_contrib_server_activity/resources/templates/activity/icons/zap.svg +1 -0
- bazaar_contrib_server_activity/resources/templates/activity/live.html +52 -0
- bazaar_contrib_server_activity/resources/templates/activity/log.html +19 -0
- bazaar_contrib_server_activity/resources/templates/activity/logos/THIRD_PARTY.md +9 -0
- bazaar_contrib_server_activity/resources/templates/activity/logos/claudecode.svg +1 -0
- bazaar_contrib_server_activity/resources/templates/activity/logos/codex.svg +1 -0
- bazaar_contrib_server_activity/resources/templates/activity/logos/lark.svg +1 -0
- bazaar_contrib_server_activity/resources/templates/activity/logos/telegram.svg +1 -0
- bazaar_contrib_server_activity/resources/templates/activity/logos/wecom.svg +1 -0
- bazaar_contrib_server_activity/resources/templates/activity/macros.html +71 -0
- bazaar_contrib_server_activity/resources/templates/activity/output.html +1 -0
- bazaar_contrib_server_activity/resources/templates/activity/rows.html +76 -0
- bazaar_contrib_server_activity/resources/templates/activity/stall.html +41 -0
- bazaar_contrib_server_activity/resources/templates/activity/today.html +34 -0
- bazaar_contrib_server_activity/resources/templates/activity/usage.html +22 -0
- bazaar_contrib_server_activity/schema.py +399 -0
- bazaar_contrib_server_activity/service.py +181 -0
- bazaar_contrib_server_activity/store.py +930 -0
- bazaar_contrib_server_activity-0.3.0rc1.dist-info/METADATA +40 -0
- bazaar_contrib_server_activity-0.3.0rc1.dist-info/RECORD +48 -0
- bazaar_contrib_server_activity-0.3.0rc1.dist-info/WHEEL +4 -0
- bazaar_contrib_server_activity-0.3.0rc1.dist-info/entry_points.txt +3 -0
- 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")
|