feat(backend): full remote implementation, untested
This commit is contained in:
1 parent
0705f4abc5
commit
5009a4eb4c
8 files changed
+485
-33
No files matched your search
+67
-9
@@ -19,6 +19,7 @@ import agents as agents_mod
|
||||
import temporary as tmp
|
||||
from server import start_server
|
||||
from chat import ChatView, Message, MESSAGE_PAGE_SIZE
|
||||
from remote import RemoteConnection, RemoteToolSpec, parse_function_specs
|
||||
|
||||
from app_mixins import ChatMixin
|
||||
|
||||
@@ -26,7 +27,8 @@ class App(ChatMixin):
|
||||
def __init__(self, data_directory, replicate_token=None, openrouter_token=None):
|
||||
self.data_directory = data_directory
|
||||
self.user_connections = {}
|
||||
self.remote_connections = {}
|
||||
# Connected remotes: (user_id, remotename) -> RemoteConnection.
|
||||
self.remotes = {}
|
||||
self.streams = {}
|
||||
self.event_listeners = []
|
||||
# active websocket tasks, for manual cancelling upon server shutdown
|
||||
@@ -81,15 +83,71 @@ class App(ChatMixin):
|
||||
if not self.user_connections[user_id]:
|
||||
del self.user_connections[user_id]
|
||||
|
||||
def add_remote_connection(self, user_id, remotename, channel):
|
||||
if user_id not in self.remote_connections:
|
||||
self.remote_connections[user_id] = {}
|
||||
user_connections = self.remote_connections[user_id]
|
||||
# --- Remotes ---
|
||||
|
||||
if remotename not in user_connections:
|
||||
user_connections[remotename] = []
|
||||
if channel not in user_connections[remotename]:
|
||||
user_connections[remotename].append(channel)
|
||||
def connectRemote(self, user_id, name, function_specs, send):
|
||||
"""Connect a remote served by a live session's send channel.
|
||||
Remotes are scoped to the user: connecting a name that is already
|
||||
connected fails (no overriding). The remote stays connected until
|
||||
disconnectRemote — the 'remote-disconnect' action or the close of
|
||||
the serving session."""
|
||||
if not name:
|
||||
raise Exception("remotename is required")
|
||||
if (user_id, name) in self.remotes:
|
||||
raise Exception(f"remote '{name}' is already connected")
|
||||
connection = RemoteConnection(
|
||||
user_id, name, parse_function_specs(function_specs), send)
|
||||
self.remotes[(user_id, name)] = connection
|
||||
self.emit_event("remote-connected", {"remotename": name, "remote": connection.describe()})
|
||||
return connection
|
||||
|
||||
def disconnectRemote(self, user_id, name):
|
||||
"""Disconnect a remote of this user; its pending calls fail.
|
||||
Idempotent: returns the connection, or None when no remote is
|
||||
connected under that name (used by both the manual disconnect
|
||||
action and session teardown)."""
|
||||
connection = self.remotes.pop((user_id, name), None)
|
||||
if connection is not None:
|
||||
connection.close()
|
||||
self.emit_event("remote-disconnected", {"remotename": name})
|
||||
return connection
|
||||
|
||||
def listRemotes(self, user_id):
|
||||
"""All connected remotes of this user."""
|
||||
return [conn.describe()
|
||||
for key, conn in self.remotes.items() if key[0] == user_id]
|
||||
|
||||
async def callRemote(self, user_id, name, function, args):
|
||||
"""Invoke a function of a connected remote and await the result.
|
||||
Raises on: unknown remote, unknown function, timeout, or an error
|
||||
reported by the remote."""
|
||||
connection = self.remotes.get((user_id, name))
|
||||
if connection is None:
|
||||
raise Exception(f"no connected remote named '{name}'")
|
||||
return await connection.call(function, args)
|
||||
|
||||
def getRemoteTools(self, user_id, remote_specs):
|
||||
"""Expand run-input remote specs into agent tools.
|
||||
|
||||
Entries are remote names, or {'name': ..., 'prefix': ...} to prefix
|
||||
the tool names. Every function of the remote becomes one tool named
|
||||
after the function itself, prefixed only when requested. Remotes
|
||||
that are not connected are skipped with a warning: they can vanish
|
||||
at any time and the run should proceed without them.
|
||||
"""
|
||||
tools = []
|
||||
for spec in remote_specs or []:
|
||||
if isinstance(spec, str):
|
||||
name, prefix = spec, ''
|
||||
else:
|
||||
name, prefix = spec.get('name'), spec.get('prefix', '')
|
||||
connection = self.remotes.get((user_id, name))
|
||||
if connection is None:
|
||||
print(f"WARNING: remote '{name}' requested by run input is not connected, skipping")
|
||||
continue
|
||||
for fn in connection.functions.values():
|
||||
tools.append(RemoteToolSpec(connection, fn, name=prefix + fn.name))
|
||||
return tools
|
||||
|
||||
# --- Agent Methods (Asynchronous) ---
|
||||
|
||||
|
||||
Reference in new issue
Block a user