bazaar-contrib-server-agent 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_agent/__init__.py +146 -0
- bazaar_contrib_server_agent/fleet.py +217 -0
- bazaar_contrib_server_agent/models.py +172 -0
- bazaar_contrib_server_agent/pages.py +1009 -0
- bazaar_contrib_server_agent/py.typed +0 -0
- bazaar_contrib_server_agent/resources/locales/en.toml +185 -0
- bazaar_contrib_server_agent/resources/locales/zh-CN.toml +185 -0
- bazaar_contrib_server_agent/resources/static/agents.css +417 -0
- bazaar_contrib_server_agent/resources/static/agents.js +39 -0
- bazaar_contrib_server_agent/resources/templates/agents/activity.html +63 -0
- bazaar_contrib_server_agent/resources/templates/agents/config.html +11 -0
- bazaar_contrib_server_agent/resources/templates/agents/detail.html +45 -0
- bazaar_contrib_server_agent/resources/templates/agents/entries.html +29 -0
- bazaar_contrib_server_agent/resources/templates/agents/fields.html +44 -0
- bazaar_contrib_server_agent/resources/templates/agents/file.html +25 -0
- bazaar_contrib_server_agent/resources/templates/agents/form.html +58 -0
- bazaar_contrib_server_agent/resources/templates/agents/icons/arrow-left.svg +1 -0
- bazaar_contrib_server_agent/resources/templates/agents/icons/bot.svg +1 -0
- bazaar_contrib_server_agent/resources/templates/agents/icons/check.svg +1 -0
- bazaar_contrib_server_agent/resources/templates/agents/icons/chevron-down.svg +1 -0
- bazaar_contrib_server_agent/resources/templates/agents/icons/chevron-right.svg +1 -0
- bazaar_contrib_server_agent/resources/templates/agents/icons/copy.svg +1 -0
- bazaar_contrib_server_agent/resources/templates/agents/icons/ellipsis.svg +1 -0
- bazaar_contrib_server_agent/resources/templates/agents/icons/file-text.svg +1 -0
- bazaar_contrib_server_agent/resources/templates/agents/icons/file.svg +1 -0
- bazaar_contrib_server_agent/resources/templates/agents/icons/folder.svg +1 -0
- bazaar_contrib_server_agent/resources/templates/agents/icons/grip-vertical.svg +1 -0
- bazaar_contrib_server_agent/resources/templates/agents/icons/key-round.svg +1 -0
- bazaar_contrib_server_agent/resources/templates/agents/icons/monitor.svg +1 -0
- bazaar_contrib_server_agent/resources/templates/agents/icons/pencil.svg +1 -0
- bazaar_contrib_server_agent/resources/templates/agents/icons/plus.svg +1 -0
- bazaar_contrib_server_agent/resources/templates/agents/icons/puzzle.svg +1 -0
- bazaar_contrib_server_agent/resources/templates/agents/icons/refresh-cw.svg +1 -0
- bazaar_contrib_server_agent/resources/templates/agents/icons/search.svg +1 -0
- bazaar_contrib_server_agent/resources/templates/agents/icons/settings.svg +1 -0
- bazaar_contrib_server_agent/resources/templates/agents/icons/share-2.svg +1 -0
- bazaar_contrib_server_agent/resources/templates/agents/icons/trash-2.svg +1 -0
- bazaar_contrib_server_agent/resources/templates/agents/icons/x.svg +1 -0
- bazaar_contrib_server_agent/resources/templates/agents/item.html +19 -0
- bazaar_contrib_server_agent/resources/templates/agents/list.html +25 -0
- bazaar_contrib_server_agent/resources/templates/agents/logos/THIRD_PARTY.md +9 -0
- bazaar_contrib_server_agent/resources/templates/agents/logos/claudecode.svg +1 -0
- bazaar_contrib_server_agent/resources/templates/agents/logos/codex.svg +1 -0
- bazaar_contrib_server_agent/resources/templates/agents/logos/lark.svg +1 -0
- bazaar_contrib_server_agent/resources/templates/agents/logos/telegram.svg +1 -0
- bazaar_contrib_server_agent/resources/templates/agents/logos/wecom.svg +1 -0
- bazaar_contrib_server_agent/resources/templates/agents/macros.html +62 -0
- bazaar_contrib_server_agent/resources/templates/agents/menu.html +5 -0
- bazaar_contrib_server_agent/resources/templates/agents/new.html +9 -0
- bazaar_contrib_server_agent/resources/templates/agents/remove.html +17 -0
- bazaar_contrib_server_agent/resources/templates/agents/rows.html +20 -0
- bazaar_contrib_server_agent/resources/templates/agents/runtimes.html +22 -0
- bazaar_contrib_server_agent/resources/templates/agents/share.html +8 -0
- bazaar_contrib_server_agent/resources/templates/agents/side.html +17 -0
- bazaar_contrib_server_agent/resources/templates/agents/skills.html +52 -0
- bazaar_contrib_server_agent/resources/templates/agents/table.html +14 -0
- bazaar_contrib_server_agent/resources/templates/agents/wizard.html +145 -0
- bazaar_contrib_server_agent/resources/templates/agents/working.html +22 -0
- bazaar_contrib_server_agent/resources/templates/agents/workspace.html +14 -0
- bazaar_contrib_server_agent/schema.py +23 -0
- bazaar_contrib_server_agent/store.py +323 -0
- bazaar_contrib_server_agent-0.3.0rc1.dist-info/METADATA +35 -0
- bazaar_contrib_server_agent-0.3.0rc1.dist-info/RECORD +66 -0
- bazaar_contrib_server_agent-0.3.0rc1.dist-info/WHEEL +4 -0
- bazaar_contrib_server_agent-0.3.0rc1.dist-info/entry_points.txt +3 -0
- bazaar_contrib_server_agent-0.3.0rc1.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""The agents of the Bazaar Compute server: every agent on its computers,
|
|
2
|
+
their pages, and who may see and change them."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from contextlib import asynccontextmanager
|
|
7
|
+
lazy import asyncio
|
|
8
|
+
lazy from collections.abc import AsyncIterator
|
|
9
|
+
lazy from importlib.resources import files
|
|
10
|
+
lazy from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from bazaar_contrib_server import (
|
|
13
|
+
SERVER,
|
|
14
|
+
PermissionItem,
|
|
15
|
+
SidebarItem,
|
|
16
|
+
SidebarPanel,
|
|
17
|
+
Topic,
|
|
18
|
+
)
|
|
19
|
+
from bazaar_contrib_server_access import ACCESS, Shared
|
|
20
|
+
from bazaar_contrib_server_computer import (
|
|
21
|
+
COMPUTER_REMOVED,
|
|
22
|
+
COMPUTERS,
|
|
23
|
+
REPORTED,
|
|
24
|
+
ComputerRemoved,
|
|
25
|
+
Reported,
|
|
26
|
+
)
|
|
27
|
+
from bazaar_contrib_server_database import SERVER_DB, SERVER_SCHEMA
|
|
28
|
+
from bazaar_core import Context, Contrib, ServiceKey
|
|
29
|
+
|
|
30
|
+
lazy from .fleet import Fleet
|
|
31
|
+
lazy from .pages import CREATE, DELETE, UPDATE, VIEW, Views
|
|
32
|
+
lazy from .schema import MODULE
|
|
33
|
+
lazy from .store import Store
|
|
34
|
+
|
|
35
|
+
__all__ = ["agent"]
|
|
36
|
+
|
|
37
|
+
# the computers' agents, for what the computers report and remove
|
|
38
|
+
_FLEET = ServiceKey["Fleet"]("server.agents.fleet")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@asynccontextmanager
|
|
42
|
+
async def _agent(ctx: Context) -> AsyncIterator[None]:
|
|
43
|
+
server = ctx.accept(SERVER)
|
|
44
|
+
access = ctx.accept(ACCESS)
|
|
45
|
+
computers = ctx.accept(COMPUTERS)
|
|
46
|
+
resources = Path(str(files(__name__))) / "resources"
|
|
47
|
+
icon = await asyncio.to_thread(
|
|
48
|
+
(resources / "templates" / "agents" / "icons" / "bot.svg").read_text,
|
|
49
|
+
encoding="utf-8",
|
|
50
|
+
)
|
|
51
|
+
async with (
|
|
52
|
+
ctx.accept(SERVER_SCHEMA).register(MODULE),
|
|
53
|
+
asyncio.TaskGroup() as tasks,
|
|
54
|
+
):
|
|
55
|
+
store = Store(ctx.accept(SERVER_DB), access)
|
|
56
|
+
fleet = Fleet(computers, store, tasks)
|
|
57
|
+
pages = await server.pages("agents", __name__)
|
|
58
|
+
views = Views(server, pages, access, computers, store, fleet)
|
|
59
|
+
async with (
|
|
60
|
+
server.module(
|
|
61
|
+
"agents",
|
|
62
|
+
ui=pages.application(views.routes),
|
|
63
|
+
static=resources / "static",
|
|
64
|
+
locales=resources / "locales",
|
|
65
|
+
),
|
|
66
|
+
server.permission(
|
|
67
|
+
PermissionItem(point=VIEW, description="agents.permission.view")
|
|
68
|
+
),
|
|
69
|
+
server.permission(
|
|
70
|
+
PermissionItem(point=CREATE, description="agents.permission.create")
|
|
71
|
+
),
|
|
72
|
+
server.permission(
|
|
73
|
+
PermissionItem(point=UPDATE, description="agents.permission.update")
|
|
74
|
+
),
|
|
75
|
+
server.permission(
|
|
76
|
+
PermissionItem(point=DELETE, description="agents.permission.delete")
|
|
77
|
+
),
|
|
78
|
+
server.sidebar(
|
|
79
|
+
SidebarItem(
|
|
80
|
+
text="agents.title",
|
|
81
|
+
icon=icon,
|
|
82
|
+
path="/agents",
|
|
83
|
+
group="server.sidebar.workbench",
|
|
84
|
+
permission=VIEW,
|
|
85
|
+
order=20,
|
|
86
|
+
badge="agents.count",
|
|
87
|
+
)
|
|
88
|
+
),
|
|
89
|
+
server.panel(
|
|
90
|
+
SidebarPanel(
|
|
91
|
+
key="agents-working",
|
|
92
|
+
src="/ui/agents/working",
|
|
93
|
+
group="server.sidebar.workbench",
|
|
94
|
+
permission=VIEW,
|
|
95
|
+
order=21,
|
|
96
|
+
)
|
|
97
|
+
),
|
|
98
|
+
server.topic(
|
|
99
|
+
Topic(name="agents.count", fetch=views.counted, permission=VIEW)
|
|
100
|
+
),
|
|
101
|
+
server.topic(
|
|
102
|
+
Topic(name="agents.list", fetch=views.listed, permission=VIEW)
|
|
103
|
+
),
|
|
104
|
+
server.topic(
|
|
105
|
+
Topic(name="agents.working", fetch=views.worked, permission=VIEW)
|
|
106
|
+
),
|
|
107
|
+
server.topic(
|
|
108
|
+
Topic(
|
|
109
|
+
name="agents.agent",
|
|
110
|
+
fetch=views.followed,
|
|
111
|
+
fields=("agent",),
|
|
112
|
+
permission=VIEW,
|
|
113
|
+
)
|
|
114
|
+
),
|
|
115
|
+
access.shared(
|
|
116
|
+
Shared(
|
|
117
|
+
kind="agent",
|
|
118
|
+
permission=VIEW,
|
|
119
|
+
points=(VIEW, UPDATE, DELETE),
|
|
120
|
+
name=views.name,
|
|
121
|
+
)
|
|
122
|
+
),
|
|
123
|
+
# who may create agents on a computer is shared with it, as 0.2 did
|
|
124
|
+
access.points("computer", (CREATE,)),
|
|
125
|
+
):
|
|
126
|
+
ctx.offer(_FLEET, fleet)
|
|
127
|
+
try:
|
|
128
|
+
yield
|
|
129
|
+
finally:
|
|
130
|
+
fleet.close()
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _reported(ctx: Context, value: Reported) -> None:
|
|
134
|
+
ctx.accept(_FLEET).reported(value.computer_id, value.event.event_name)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
async def _removed(ctx: Context, value: ComputerRemoved) -> None:
|
|
138
|
+
await ctx.accept(_FLEET).removed(value.computer_id)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
agent = Contrib(
|
|
142
|
+
accepts=[SERVER, ACCESS, COMPUTERS, SERVER_DB, SERVER_SCHEMA],
|
|
143
|
+
offers=[_FLEET],
|
|
144
|
+
lifespan=_agent,
|
|
145
|
+
listen={REPORTED: _reported, COMPUTER_REMOVED: _removed},
|
|
146
|
+
)
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"""What each computer's node says about its agents: the list from
|
|
2
|
+
agent-configuration, kept in the store, and their state from
|
|
3
|
+
agent-activity; asked while a page shows the computer once what was said
|
|
4
|
+
is a minute old, the state again as soon as the computer reports a turn or
|
|
5
|
+
a connection, once more when it reports one while an answer is on its way,
|
|
6
|
+
and what a runtime reports kept a minute for each agent and kind."""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
lazy import asyncio
|
|
12
|
+
lazy import logging
|
|
13
|
+
lazy from time import time_ns
|
|
14
|
+
|
|
15
|
+
from pydantic import TypeAdapter, ValidationError
|
|
16
|
+
lazy from bazaar_contrib_server_computer import Computers
|
|
17
|
+
lazy from bazaar_contrib_sidecar import RemoteError
|
|
18
|
+
|
|
19
|
+
from .models import Activity, Inspection, Listing, Providers
|
|
20
|
+
lazy from .store import Store
|
|
21
|
+
|
|
22
|
+
# what was said is shown for a minute before the node is asked again
|
|
23
|
+
FRESH_MS = 60_000
|
|
24
|
+
# what a computer reports that changes the state of one of its agents
|
|
25
|
+
CHANGES = frozenset(
|
|
26
|
+
{
|
|
27
|
+
"harness.started",
|
|
28
|
+
"harness.completed",
|
|
29
|
+
"harness.failed",
|
|
30
|
+
"harness.cancelled",
|
|
31
|
+
"channel.connection.connected",
|
|
32
|
+
"channel.connection.failed",
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
_ACTIVITY = TypeAdapter(dict[str, Activity])
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _now() -> int:
|
|
40
|
+
return time_ns() // 1_000_000
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True, slots=True)
|
|
44
|
+
class Inspected:
|
|
45
|
+
"""What a runtime reported, and when it was asked."""
|
|
46
|
+
|
|
47
|
+
inspection: Inspection
|
|
48
|
+
at_ms: int
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class Fleet:
|
|
52
|
+
def __init__(
|
|
53
|
+
self, computers: Computers, store: Store, tasks: asyncio.TaskGroup
|
|
54
|
+
) -> None:
|
|
55
|
+
self._computers = computers
|
|
56
|
+
self._store = store
|
|
57
|
+
self._tasks = tasks
|
|
58
|
+
self._listed: dict[str, tuple[int, str | None]] = {}
|
|
59
|
+
self._activity: dict[str, tuple[int, dict[str, Activity]]] = {}
|
|
60
|
+
self._inspected: dict[tuple[str, str | None, str], Inspected] = {}
|
|
61
|
+
self._providers: dict[str, tuple[int, Providers]] = {}
|
|
62
|
+
self._asking: dict[tuple[str, str], asyncio.Task[None]] = {}
|
|
63
|
+
# computers that reported a change while their state was being asked
|
|
64
|
+
self._again: set[str] = set()
|
|
65
|
+
|
|
66
|
+
def look(self, computer_id: str, online: bool) -> None:
|
|
67
|
+
"""A page shows the computer's agents: one online is asked again
|
|
68
|
+
about what is a minute old."""
|
|
69
|
+
if not online:
|
|
70
|
+
return
|
|
71
|
+
now = _now()
|
|
72
|
+
listed = self._listed.get(computer_id)
|
|
73
|
+
if listed is None or now - listed[0] >= FRESH_MS:
|
|
74
|
+
self._ask(computer_id, "list")
|
|
75
|
+
known = self._activity.get(computer_id)
|
|
76
|
+
if known is None or now - known[0] >= FRESH_MS:
|
|
77
|
+
self._ask(computer_id, "activity")
|
|
78
|
+
|
|
79
|
+
def reported(self, computer_id: str, event_name: str) -> None:
|
|
80
|
+
"""A change the computer reports: its state is asked again, and asked
|
|
81
|
+
once more after an answer on its way, which may have been taken
|
|
82
|
+
before the change."""
|
|
83
|
+
if event_name not in CHANGES:
|
|
84
|
+
return
|
|
85
|
+
if (computer_id, "activity") in self._asking:
|
|
86
|
+
self._again.add(computer_id)
|
|
87
|
+
else:
|
|
88
|
+
self._ask(computer_id, "activity")
|
|
89
|
+
|
|
90
|
+
def revision(self, computer_id: str) -> str | None:
|
|
91
|
+
"""The revision of the configuration the computer last gave its list
|
|
92
|
+
from, which changes as any of its agents is changed."""
|
|
93
|
+
listed = self._listed.get(computer_id)
|
|
94
|
+
return None if listed is None else listed[1]
|
|
95
|
+
|
|
96
|
+
def activity(self, computer_id: str) -> dict[str, Activity]:
|
|
97
|
+
"""The state of the computer's agents as it last said, when it did."""
|
|
98
|
+
known = self._activity.get(computer_id)
|
|
99
|
+
return {} if known is None else known[1]
|
|
100
|
+
|
|
101
|
+
def working(self) -> list[tuple[str, str, Activity]]:
|
|
102
|
+
"""The agents at work as their computers last said: their computer,
|
|
103
|
+
their id and how they are."""
|
|
104
|
+
return [
|
|
105
|
+
(computer_id, agent_id, activity)
|
|
106
|
+
for computer_id, (_, agents) in self._activity.items()
|
|
107
|
+
for agent_id, activity in agents.items()
|
|
108
|
+
if activity.working is not None
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
def said(self) -> dict[str, str]:
|
|
112
|
+
"""How each agent is as its computer last said, today's usage with
|
|
113
|
+
it, put shortly enough to tell when it changes."""
|
|
114
|
+
return {
|
|
115
|
+
agent_id: "/".join(
|
|
116
|
+
(
|
|
117
|
+
activity.state,
|
|
118
|
+
"" if activity.working is None else str(activity.working.since_ms),
|
|
119
|
+
*(channel.state for channel in activity.channels),
|
|
120
|
+
*(runtime.state for runtime in activity.runtimes),
|
|
121
|
+
activity.usage.model_dump_json(),
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
for _, agents in self._activity.values()
|
|
125
|
+
for agent_id, activity in agents.items()
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
def _ask(self, computer_id: str, what: str) -> None:
|
|
129
|
+
if (computer_id, what) not in self._asking:
|
|
130
|
+
self._asking[computer_id, what] = self._tasks.create_task(
|
|
131
|
+
self._answer(computer_id, what)
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
async def _answer(self, computer_id: str, what: str) -> None:
|
|
135
|
+
try:
|
|
136
|
+
if what == "list":
|
|
137
|
+
await self.listing(computer_id)
|
|
138
|
+
else:
|
|
139
|
+
again = True
|
|
140
|
+
while again:
|
|
141
|
+
self._again.discard(computer_id)
|
|
142
|
+
value = await self._computers.call(
|
|
143
|
+
computer_id, "agent-activity", "agents"
|
|
144
|
+
)
|
|
145
|
+
self._activity[computer_id] = (
|
|
146
|
+
_now(),
|
|
147
|
+
_ACTIVITY.validate_python(value),
|
|
148
|
+
)
|
|
149
|
+
again = computer_id in self._again
|
|
150
|
+
except RemoteError, TimeoutError, ValidationError:
|
|
151
|
+
# a node that does not answer, or answers otherwise, keeps what
|
|
152
|
+
# it said before
|
|
153
|
+
logging.getLogger(__name__).info("agents.unanswered", exc_info=True)
|
|
154
|
+
finally:
|
|
155
|
+
del self._asking[computer_id, what]
|
|
156
|
+
|
|
157
|
+
async def listing(self, computer_id: str) -> Listing:
|
|
158
|
+
"""The computer's agents as it gives them now, kept for when it is away."""
|
|
159
|
+
asked = _now()
|
|
160
|
+
listing = Listing.model_validate(
|
|
161
|
+
await self._computers.call(computer_id, "agent-configuration", "list")
|
|
162
|
+
)
|
|
163
|
+
now = _now()
|
|
164
|
+
await self._store.replace(computer_id, listing.agents, asked, now)
|
|
165
|
+
self._listed[computer_id] = (now, listing.revision)
|
|
166
|
+
return listing
|
|
167
|
+
|
|
168
|
+
async def inspect(
|
|
169
|
+
self, computer_id: str, kind: str, agent_id: str | None = None
|
|
170
|
+
) -> Inspected:
|
|
171
|
+
"""What the runtime of the kind reports in the agent's workspace, or
|
|
172
|
+
in an empty one, asked again once a minute old."""
|
|
173
|
+
key = (computer_id, agent_id, kind)
|
|
174
|
+
known = self._inspected.get(key)
|
|
175
|
+
if known is not None and _now() - known.at_ms < FRESH_MS:
|
|
176
|
+
return known
|
|
177
|
+
inspected = Inspected(
|
|
178
|
+
inspection=Inspection.model_validate(
|
|
179
|
+
await self._computers.call(
|
|
180
|
+
computer_id, "agent-configuration", "inspect", kind, agent_id
|
|
181
|
+
)
|
|
182
|
+
),
|
|
183
|
+
at_ms=_now(),
|
|
184
|
+
)
|
|
185
|
+
self._inspected[key] = inspected
|
|
186
|
+
return inspected
|
|
187
|
+
|
|
188
|
+
async def providers(self, computer_id: str) -> Providers:
|
|
189
|
+
"""The channels and runtimes the computer can run, with the schemas
|
|
190
|
+
their forms are drawn from, asked again once a minute old."""
|
|
191
|
+
known = self._providers.get(computer_id)
|
|
192
|
+
if known is not None and _now() - known[0] < FRESH_MS:
|
|
193
|
+
return known[1]
|
|
194
|
+
providers = Providers.model_validate(
|
|
195
|
+
await self._computers.call(computer_id, "agent-configuration", "providers")
|
|
196
|
+
)
|
|
197
|
+
self._providers[computer_id] = (_now(), providers)
|
|
198
|
+
return providers
|
|
199
|
+
|
|
200
|
+
async def removed(self, computer_id: str) -> None:
|
|
201
|
+
"""The computer is gone, and its agents with it."""
|
|
202
|
+
now = _now()
|
|
203
|
+
await self._store.replace(computer_id, (), now, now)
|
|
204
|
+
self.forget(computer_id)
|
|
205
|
+
|
|
206
|
+
def forget(self, computer_id: str) -> None:
|
|
207
|
+
self._listed.pop(computer_id, None)
|
|
208
|
+
self._activity.pop(computer_id, None)
|
|
209
|
+
self._providers.pop(computer_id, None)
|
|
210
|
+
self._again.discard(computer_id)
|
|
211
|
+
for key in [key for key in self._inspected if key[0] == computer_id]:
|
|
212
|
+
del self._inspected[key]
|
|
213
|
+
|
|
214
|
+
def close(self) -> None:
|
|
215
|
+
"""Stop asking: the server is going down."""
|
|
216
|
+
for task in self._asking.values():
|
|
217
|
+
task.cancel()
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""What a computer's node answers about its agents, as agent-configuration
|
|
2
|
+
and agent-activity give it."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import Literal
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, ConfigDict, JsonValue
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class _Answer(BaseModel):
|
|
12
|
+
model_config = ConfigDict(frozen=True)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Provider(_Answer):
|
|
16
|
+
"""A channel or runtime of an agent: its values, and the names of the
|
|
17
|
+
credentials it has, never their values."""
|
|
18
|
+
|
|
19
|
+
kind: str
|
|
20
|
+
connection: str
|
|
21
|
+
values: dict[str, JsonValue]
|
|
22
|
+
secrets: tuple[str, ...]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Sandbox(_Answer):
|
|
26
|
+
policy: Literal["workspace-write", "danger-full-access"] = "workspace-write"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SessionConfig(_Answer):
|
|
30
|
+
sandbox: Sandbox = Sandbox()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Session(_Answer):
|
|
34
|
+
config: SessionConfig = SessionConfig()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class AgentView(_Answer):
|
|
38
|
+
id: str
|
|
39
|
+
name: str
|
|
40
|
+
mode: Literal["session", "dangerous_individual"]
|
|
41
|
+
session: Session | None
|
|
42
|
+
idle_timeout: float
|
|
43
|
+
channel: tuple[Provider, ...]
|
|
44
|
+
harness: tuple[Provider, ...]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Listing(_Answer):
|
|
48
|
+
revision: str | None
|
|
49
|
+
agents: tuple[AgentView, ...]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Schema(_Answer):
|
|
53
|
+
kind: str
|
|
54
|
+
json_schema: dict[str, JsonValue]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class Providers(_Answer):
|
|
58
|
+
channel: tuple[Schema, ...]
|
|
59
|
+
harness: tuple[Schema, ...]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class Usage(_Answer):
|
|
63
|
+
input: int = 0
|
|
64
|
+
output: int = 0
|
|
65
|
+
cached: int = 0
|
|
66
|
+
cost: float | None = None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class Working(_Answer):
|
|
70
|
+
since_ms: int
|
|
71
|
+
conversation: str | None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class Identity(_Answer):
|
|
75
|
+
id: str
|
|
76
|
+
name: str | None = None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class ChannelStatus(_Answer):
|
|
80
|
+
kind: str
|
|
81
|
+
connection: str
|
|
82
|
+
state: str
|
|
83
|
+
identity: Identity | None
|
|
84
|
+
connected_at_ms: int | None
|
|
85
|
+
received_at_ms: int | None
|
|
86
|
+
failures: int
|
|
87
|
+
error: str | None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class RuntimeStatus(_Answer):
|
|
91
|
+
kind: str
|
|
92
|
+
connection: str
|
|
93
|
+
state: str
|
|
94
|
+
in_use: bool
|
|
95
|
+
banned_until_ms: int | None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class Activity(_Answer):
|
|
99
|
+
state: str
|
|
100
|
+
reason: str | None
|
|
101
|
+
usage: Usage
|
|
102
|
+
event: str | None
|
|
103
|
+
event_at_ms: int | None
|
|
104
|
+
started_at_ms: int | None
|
|
105
|
+
working: Working | None
|
|
106
|
+
channels: tuple[ChannelStatus, ...]
|
|
107
|
+
runtimes: tuple[RuntimeStatus, ...]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class Model(_Answer):
|
|
111
|
+
id: str
|
|
112
|
+
name: str
|
|
113
|
+
efforts: tuple[str, ...] = ()
|
|
114
|
+
default_effort: str | None = None
|
|
115
|
+
default: bool = False
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class Skill(_Answer):
|
|
119
|
+
name: str
|
|
120
|
+
description: str
|
|
121
|
+
source: str
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class Tool(_Answer):
|
|
125
|
+
name: str
|
|
126
|
+
description: str | None = None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class McpFailed(_Answer):
|
|
130
|
+
"""A runtime could not connect to the server, and why."""
|
|
131
|
+
|
|
132
|
+
reason: str
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class McpServer(_Answer):
|
|
136
|
+
"""An MCP server as a runtime sees it, in the common states; what the
|
|
137
|
+
runtime does not say is left out."""
|
|
138
|
+
|
|
139
|
+
name: str
|
|
140
|
+
connection: Literal["connected", "connecting", "disabled"] | McpFailed | None = None
|
|
141
|
+
auth: Literal["none", "signed_in", "signed_out"] | None = None
|
|
142
|
+
tools: tuple[Tool, ...] = ()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class Inspection(_Answer):
|
|
146
|
+
available: bool
|
|
147
|
+
version: str | None = None
|
|
148
|
+
models: tuple[Model, ...] = ()
|
|
149
|
+
skills: tuple[Skill, ...] = ()
|
|
150
|
+
mcp_servers: tuple[McpServer, ...] = ()
|
|
151
|
+
error: str | None = None
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class Entry(_Answer):
|
|
155
|
+
name: str
|
|
156
|
+
directory: bool
|
|
157
|
+
size: int
|
|
158
|
+
modified_at_ms: int
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class Directory(_Answer):
|
|
162
|
+
root: str
|
|
163
|
+
path: str
|
|
164
|
+
total: int
|
|
165
|
+
entries: tuple[Entry, ...]
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class File(_Answer):
|
|
169
|
+
path: str
|
|
170
|
+
size: int
|
|
171
|
+
modified_at_ms: int
|
|
172
|
+
content: str | None
|