jvserve 2.0.16__tar.gz → 2.1.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.

Potentially problematic release.


This version of jvserve might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jvserve
3
- Version: 2.0.16
3
+ Version: 2.1.2
4
4
  Summary: FastAPI webserver for loading and interaction with JIVAS agents.
5
5
  Home-page: https://github.com/TrueSelph/jvserve
6
6
  Author: TrueSelph Inc.
@@ -9,7 +9,8 @@ Keywords: jivas
9
9
  Requires-Python: >=3.12.0
10
10
  Description-Content-Type: text/markdown
11
11
  License-File: LICENSE
12
- Requires-Dist: jac-cloud==0.1.20
12
+ Requires-Dist: jac-cloud
13
+ Requires-Dist: jaclang==0.8.4
13
14
  Requires-Dist: pyaml>=25.1.0
14
15
  Requires-Dist: requests>=2.32.3
15
16
  Requires-Dist: aiohttp>=3.10.10
@@ -80,7 +81,7 @@ jac jvfileserve ./static
80
81
  ### Supported Arguments
81
82
 
82
83
  - **filename**: Path to your JAC file.
83
- - **host**: Host address to bind the server (default: `0.0.0.0`).
84
+ - **host**: Host address to bind the server (default: `localhost`).
84
85
  - **port**: Port number to bind the server (default: `8000`).
85
86
  - **loglevel**: Logging level (default: `INFO`).
86
87
  - **workers**: Number of worker processes (optional).
@@ -45,7 +45,7 @@ jac jvfileserve ./static
45
45
  ### Supported Arguments
46
46
 
47
47
  - **filename**: Path to your JAC file.
48
- - **host**: Host address to bind the server (default: `0.0.0.0`).
48
+ - **host**: Host address to bind the server (default: `localhost`).
49
49
  - **port**: Port number to bind the server (default: `8000`).
50
50
  - **loglevel**: Logging level (default: `INFO`).
51
51
  - **workers**: Number of worker processes (optional).
@@ -4,5 +4,7 @@ jvserve package initialization.
4
4
  This package provides the webserver for loading and interacting with JIVAS agents.
5
5
  """
6
6
 
7
- __version__ = "2.0.16"
8
- __supported__jivas__versions__ = ["2.0.0"]
7
+ from importlib.metadata import version
8
+
9
+ __version__ = version("jvserve")
10
+ __supported__jivas__versions__ = [__version__]
@@ -0,0 +1,256 @@
1
+ """Module for registering CLI plugins for jaseci."""
2
+
3
+ import asyncio
4
+ import logging
5
+ import os
6
+ from concurrent.futures import ThreadPoolExecutor
7
+ from contextlib import asynccontextmanager
8
+ from pickle import load
9
+ from typing import AsyncIterator, Optional
10
+
11
+ import aiohttp
12
+ import pymongo
13
+ from bson import ObjectId
14
+ from dotenv import load_dotenv
15
+ from fastapi import FastAPI, HTTPException, Response
16
+ from fastapi.middleware.cors import CORSMiddleware
17
+ from fastapi.responses import FileResponse, StreamingResponse
18
+ from jac_cloud.core.context import JaseciContext
19
+ from jac_cloud.jaseci.main import FastAPI as JaseciFastAPI # type: ignore
20
+ from jac_cloud.plugin.jaseci import NodeAnchor
21
+ from jaclang import JacMachine as Jac
22
+ from jaclang.cli.cmdreg import cmd_registry
23
+ from jaclang.runtimelib.machine import hookimpl
24
+ from watchfiles import Change, run_process
25
+
26
+ from jvserve.lib.agent_interface import AgentInterface
27
+ from jvserve.lib.file_interface import (
28
+ DEFAULT_FILES_ROOT,
29
+ FILE_INTERFACE,
30
+ file_interface,
31
+ )
32
+ from jvserve.lib.jvlogger import JVLogger
33
+
34
+ # quiet the jac_cloud logger down to errors only
35
+ # jac cloud dumps payload details to console which makes it hard to debug in JIVAS
36
+ os.environ["LOGGER_LEVEL"] = "ERROR"
37
+ load_dotenv(".env")
38
+ # Set up logging
39
+ JVLogger.setup_logging(level="INFO")
40
+ logger = logging.getLogger(__name__)
41
+
42
+ # Global for MongoDB collection with thread-safe initialization
43
+ url_proxy_collection = None
44
+ collection_init_lock = asyncio.Lock()
45
+
46
+
47
+ async def get_url_proxy_collection() -> pymongo.collection.Collection:
48
+ """Thread-safe initialization of MongoDB collection"""
49
+ global url_proxy_collection
50
+ if url_proxy_collection is None:
51
+ async with collection_init_lock:
52
+ if url_proxy_collection is None: # Double-check locking
53
+ loop = asyncio.get_running_loop()
54
+ with ThreadPoolExecutor() as pool:
55
+ url_proxy_collection = await loop.run_in_executor(
56
+ pool,
57
+ lambda: NodeAnchor.Collection.get_collection("url_proxies"),
58
+ )
59
+ return url_proxy_collection
60
+
61
+
62
+ async def serve_proxied_file(
63
+ file_path: str,
64
+ ) -> FileResponse | StreamingResponse | Response:
65
+ """Serve a proxied file from a remote or local URL (async version)"""
66
+ if FILE_INTERFACE == "local":
67
+ root_path = os.environ.get("JIVAS_FILES_ROOT_PATH", DEFAULT_FILES_ROOT)
68
+ full_path = os.path.join(root_path, file_path)
69
+ if not os.path.exists(full_path):
70
+ raise HTTPException(status_code=404, detail="File not found")
71
+ return FileResponse(full_path)
72
+
73
+ file_url = file_interface.get_file_url(file_path)
74
+
75
+ # Security check to prevent recursive calls
76
+ if file_url and ("localhost" in file_url or "127.0.0.1" in file_url):
77
+ raise HTTPException(
78
+ status_code=500, detail="Environment misconfiguration detected"
79
+ )
80
+
81
+ if not file_url:
82
+ raise HTTPException(status_code=404, detail="File not found")
83
+
84
+ try:
85
+ async with aiohttp.ClientSession() as session:
86
+ async with session.get(file_url) as response:
87
+ response.raise_for_status()
88
+
89
+ return StreamingResponse(
90
+ response.content.iter_chunked(8192),
91
+ media_type=response.headers.get(
92
+ "Content-Type", "application/octet-stream"
93
+ ),
94
+ )
95
+ except aiohttp.ClientError as e:
96
+ raise HTTPException(status_code=502, detail=f"File fetch error: {str(e)}")
97
+
98
+
99
+ def run_jivas(filename: str, host: str = "localhost", port: int = 8000) -> None:
100
+ """Starts JIVAS server with integrated file services"""
101
+
102
+ # Create agent interface instance with configuration
103
+ agent_interface = AgentInterface.get_instance(host=host, port=port)
104
+
105
+ base, mod = os.path.split(filename)
106
+ base = base if base else "./"
107
+ mod = mod[:-4]
108
+
109
+ JaseciFastAPI.enable()
110
+
111
+ ctx = JaseciContext.create(None)
112
+ if filename.endswith(".jac"):
113
+ Jac.jac_import(target=mod, base_path=base, override_name="__main__")
114
+ elif filename.endswith(".jir"):
115
+ with open(filename, "rb") as f:
116
+ Jac.attach_program(load(f))
117
+ Jac.jac_import(target=mod, base_path=base, override_name="__main__")
118
+ else:
119
+ raise ValueError("Not a valid file!\nOnly supports `.jac` and `.jir`")
120
+
121
+ # Define post-startup function to run AFTER server is ready
122
+ async def post_startup() -> None:
123
+ """Wait for server to be ready before initializing agents"""
124
+ health_url = f"http://{host}:{port}/healthz"
125
+ max_retries = 10
126
+ retry_delay = 1.0
127
+
128
+ for attempt in range(max_retries):
129
+ try:
130
+ async with aiohttp.ClientSession() as session:
131
+ async with session.get(health_url, timeout=1) as response:
132
+ if response.status == 200:
133
+ logger.info("Server is ready, initializing agents...")
134
+ await agent_interface.init_agents()
135
+ return
136
+ except (aiohttp.ClientConnectorError, asyncio.TimeoutError) as e:
137
+ logger.warning(
138
+ f"Server not ready yet (attempt {attempt + 1} / {max_retries}): {e}"
139
+ )
140
+ await asyncio.sleep(retry_delay)
141
+ retry_delay *= 1.5 # Exponential backoff
142
+
143
+ logger.error(
144
+ "Server did not become ready in time. Agent initialization skipped."
145
+ )
146
+
147
+ # set up lifespan events
148
+ async def on_startup() -> None:
149
+ logger.info("JIVAS is starting up...")
150
+ # Start initialization in background without blocking
151
+ asyncio.create_task(post_startup())
152
+
153
+ async def on_shutdown() -> None:
154
+ logger.info("JIVAS is shutting down...")
155
+
156
+ app = JaseciFastAPI.get()
157
+ app_lifespan = app.router.lifespan_context
158
+
159
+ @asynccontextmanager
160
+ async def lifespan_wrapper(app: FastAPI) -> AsyncIterator[Optional[str]]:
161
+ await on_startup()
162
+ async with app_lifespan(app) as maybe_state:
163
+ yield maybe_state
164
+ await on_shutdown()
165
+
166
+ app.router.lifespan_context = lifespan_wrapper
167
+
168
+ # Add CORS middleware to main app
169
+ app.add_middleware(
170
+ CORSMiddleware,
171
+ allow_origins=["*"],
172
+ allow_credentials=True,
173
+ allow_methods=["*"],
174
+ allow_headers=["*"],
175
+ )
176
+
177
+ # Ensure the local file directory exists if that's the interface
178
+ if FILE_INTERFACE == "local":
179
+ directory = os.environ.get("JIVAS_FILES_ROOT_PATH", DEFAULT_FILES_ROOT)
180
+ if not os.path.exists(directory):
181
+ os.makedirs(directory, exist_ok=True)
182
+
183
+ # Setup file serving endpoint for both local and S3
184
+ @app.get("/files/{file_path:path}", response_model=None)
185
+ async def serve_file(
186
+ file_path: str,
187
+ ) -> FileResponse | StreamingResponse | Response:
188
+ # The serve_proxied_file function already handles both local and S3 cases
189
+ return await serve_proxied_file(file_path)
190
+
191
+ # Setup URL proxy endpoint
192
+ @app.get("/f/{file_id:path}", response_model=None)
193
+ async def get_proxied_file(
194
+ file_id: str,
195
+ ) -> FileResponse | StreamingResponse | Response:
196
+ params = file_id.split("/")
197
+ object_id = params[0]
198
+
199
+ try:
200
+ # Get MongoDB collection (thread-safe initialization)
201
+ collection = await get_url_proxy_collection()
202
+
203
+ # Run blocking MongoDB operation in thread pool
204
+ loop = asyncio.get_running_loop()
205
+ file_details = await loop.run_in_executor(
206
+ None, lambda: collection.find_one({"_id": ObjectId(object_id)})
207
+ )
208
+
209
+ descriptor_path = os.environ.get("JIVAS_DESCRIPTOR_ROOT_PATH")
210
+
211
+ if file_details:
212
+ if descriptor_path and descriptor_path in file_details["path"]:
213
+ return Response(status_code=403)
214
+ return await serve_proxied_file(file_details["path"])
215
+
216
+ raise HTTPException(status_code=404, detail="File not found")
217
+ except Exception as e:
218
+ logger.error(f"Proxy error: {str(e)}")
219
+ raise HTTPException(status_code=500, detail="Internal server error")
220
+
221
+ ctx.close()
222
+ # Run the app
223
+ JaseciFastAPI.start(host=host, port=port)
224
+
225
+
226
+ def log_reload(changes: set[tuple[Change, str]]) -> None:
227
+ """Log changes."""
228
+ num_of_changes = len(changes)
229
+ logger.warning(
230
+ f'Detected {num_of_changes} change{"s" if num_of_changes > 1 else ""}'
231
+ )
232
+ for change in changes:
233
+ logger.warning(f"{change[1]} ({change[0].name})")
234
+ logger.warning("Reloading ...")
235
+
236
+
237
+ class JacCmd:
238
+ """Jac CLI."""
239
+
240
+ @staticmethod
241
+ @hookimpl
242
+ def create_cmd() -> None:
243
+ """Create Jac CLI cmds."""
244
+
245
+ @cmd_registry.register
246
+ def jvserve(filename: str, host: str = "localhost", port: int = 8000) -> None:
247
+ """Launch unified JIVAS server with file services"""
248
+ watchdir = os.path.join(
249
+ os.path.abspath(os.path.dirname(filename)), "actions", ""
250
+ )
251
+ run_process(
252
+ watchdir,
253
+ target=run_jivas,
254
+ args=(filename, host, port),
255
+ callback=log_reload,
256
+ )
@@ -0,0 +1,97 @@
1
+ """Agent Interface class and methods for interaction with Jivas."""
2
+
3
+ import logging
4
+ import os
5
+ import traceback
6
+ from typing import Any
7
+
8
+ import requests
9
+
10
+ from jvserve.lib.jac_interface import JacInterface
11
+
12
+
13
+ class AgentInterface:
14
+ """Agent Interface for Jivas with proper concurrency handling."""
15
+
16
+ _instance = None
17
+ logger = logging.getLogger(__name__)
18
+
19
+ def __init__(self, host: str = "localhost", port: int = 8000) -> None:
20
+ """Initialize the AgentInterface with JacInterface."""
21
+ self._jac = JacInterface(host, port)
22
+
23
+ @classmethod
24
+ def get_instance(
25
+ cls, host: str = "localhost", port: int = 8000
26
+ ) -> "AgentInterface":
27
+ """Get a singleton instance of AgentInterface."""
28
+ if cls._instance is None:
29
+ env_host = os.environ.get("JIVAS_HOST", "localhost")
30
+ env_port = int(os.environ.get("JIVAS_PORT", "8000"))
31
+ host = host or env_host
32
+ port = port or env_port
33
+ cls._instance = cls(host, port)
34
+ return cls._instance
35
+
36
+ async def init_agents(self) -> None:
37
+ """Initialize agents - async compatible"""
38
+ try:
39
+ if not await self._jac.spawn_walker_async(
40
+ walker_name="init_agents",
41
+ module_name="jivas.agent.core.init_agents",
42
+ attributes={"reporting": False},
43
+ ):
44
+ self.logger.error("Agent initialization failed")
45
+ except Exception as e:
46
+ self._jac.reset()
47
+ self.logger.error(f"Init error: {e}\n{traceback.format_exc()}")
48
+
49
+ def api_pulse(self, action_label: str, agent_id: str) -> dict:
50
+ """Synchronous pulse API call"""
51
+ if not self._jac.is_valid():
52
+ self.logger.warning("Invalid API state for pulse")
53
+ return {}
54
+
55
+ # Clean parameters
56
+ action_label = action_label.replace("action_label=", "")
57
+ agent_id = agent_id.replace("agent_id=", "")
58
+
59
+ endpoint = f"http://{self._jac.host}:{self._jac.port}/walker/do_pulse"
60
+ headers = {"Authorization": f"Bearer {self._jac.token}"}
61
+ payload = {"action_label": action_label, "agent_id": agent_id}
62
+
63
+ try:
64
+ response = requests.post(
65
+ endpoint, json=payload, headers=headers, timeout=10
66
+ )
67
+ if response.status_code == 200:
68
+ return response.json().get("reports", {})
69
+ if response.status_code == 401:
70
+ self._jac.reset()
71
+ except Exception as e:
72
+ self._jac.reset()
73
+ self.logger.error(f"Pulse error: {e}\n{traceback.format_exc()}")
74
+
75
+ return {}
76
+
77
+ async def _finalize_interaction(
78
+ self, interaction_node: Any, full_text: str, total_tokens: int
79
+ ) -> None:
80
+ """Finalize interaction in background"""
81
+ try:
82
+ interaction_node.set_text_message(message=full_text)
83
+ interaction_node.add_tokens(total_tokens)
84
+
85
+ await self._jac.spawn_walker_async(
86
+ walker_name="update_interaction",
87
+ module_name="jivas.agent.memory.update_interaction",
88
+ attributes={"interaction_data": interaction_node.export()},
89
+ )
90
+ except Exception as e:
91
+ self.logger.error(f"Finalize error: {e}")
92
+
93
+
94
+ # Module-level functions
95
+ def do_pulse(action_label: str, agent_id: str) -> dict:
96
+ """Execute pulse action synchronously"""
97
+ return AgentInterface.get_instance().api_pulse(action_label, agent_id)
@@ -78,7 +78,7 @@ class LocalFileInterface(FileInterface):
78
78
  """Get URL for accessing local file via HTTP."""
79
79
  file_path = os.path.join(self.__root_dir, filename)
80
80
  if os.path.exists(file_path):
81
- return f"{os.environ.get('JIVAS_FILES_URL', 'http://localhost:9000/files')}/{filename}"
81
+ return f"{os.environ.get('JIVAS_FILES_URL', 'http://localhost:8000/files')}/{filename}"
82
82
  return None
83
83
 
84
84
 
@@ -0,0 +1,222 @@
1
+ """JacInterface: A connection and context state provider for Jac Runtime."""
2
+
3
+ import asyncio
4
+ import logging
5
+ import os
6
+ import threading
7
+ import time
8
+ import traceback
9
+ from typing import Optional, Tuple
10
+
11
+ import requests
12
+ from fastapi import Request
13
+ from jac_cloud.core.archetype import ( # type: ignore
14
+ NodeAnchor,
15
+ WalkerArchetype,
16
+ )
17
+ from jac_cloud.core.context import (
18
+ JASECI_CONTEXT,
19
+ JaseciContext,
20
+ )
21
+ from jac_cloud.plugin.jaseci import JacPlugin
22
+ from jaclang.runtimelib.machine import JacMachine
23
+
24
+
25
+ class JacInterface:
26
+ """Thread-safe connection and context state provider for Jac Runtime with auto-authentication."""
27
+
28
+ def __init__(self, host: str = "localhost", port: int = 8000) -> None:
29
+ """Initialize JacInterface with host and port."""
30
+ self.host = host
31
+ self.port = port
32
+ self.root_id = ""
33
+ self.token = ""
34
+ self.expiration = 0.0
35
+ self._lock = threading.RLock() # Thread-safe lock
36
+ self.logger = logging.getLogger(__name__)
37
+
38
+ def update(self, root_id: str, token: str, expiration: float) -> None:
39
+ """Thread-safe state update"""
40
+ with self._lock:
41
+ self.root_id = root_id
42
+ self.token = token
43
+ self.expiration = expiration
44
+
45
+ def reset(self) -> None:
46
+ """Thread-safe state reset"""
47
+ with self._lock:
48
+ self.root_id = ""
49
+ self.token = ""
50
+ self.expiration = 0.0
51
+
52
+ def is_valid(self) -> bool:
53
+ """Thread-safe validity check"""
54
+ with self._lock:
55
+ return bool(
56
+ self.token and self.expiration and self.expiration > time.time()
57
+ )
58
+
59
+ def get_state(self) -> Tuple[str, str, Optional[float]]:
60
+ """Get current state with auto-authentication"""
61
+ if not self.is_valid():
62
+ self._authenticate()
63
+ with self._lock:
64
+ return (self.root_id, self.token, self.expiration)
65
+
66
+ def get_context(self, request: Request | None = None) -> Optional[JaseciContext]:
67
+ """Get Jaseci context with proper thread safety."""
68
+
69
+ state = self.get_state()
70
+ if not state or not self.is_valid():
71
+ self.logger.error("Failed to get valid state for Jaseci context")
72
+ return None
73
+
74
+ try:
75
+ root_id = state[0]
76
+ entry_node = NodeAnchor.ref(f"n:root:{root_id}") # type: ignore
77
+ if not entry_node:
78
+ self.logger.error("Failed to resolve entry node from root_id")
79
+ return None
80
+
81
+ ctx = JaseciContext.create(request, entry_node)
82
+ if not ctx:
83
+ self.logger.error("Failed to create JaseciContext with entry node")
84
+ return None
85
+
86
+ ctx.system_root = entry_node
87
+ ctx.root_state = entry_node
88
+
89
+ # Clean up any existing context before setting new one
90
+ existing_ctx = JASECI_CONTEXT.get(None)
91
+ if existing_ctx:
92
+ try:
93
+ existing_ctx.close()
94
+ except Exception as e:
95
+ self.logger.warning(f"Error while closing existing context: {e}")
96
+
97
+ JASECI_CONTEXT.set(ctx)
98
+ return ctx
99
+
100
+ except Exception as e:
101
+ self.logger.error(
102
+ f"Failed to create JaseciContext: {e}\n{traceback.format_exc()}"
103
+ )
104
+ return None
105
+
106
+ def spawn_walker(
107
+ self,
108
+ walker_name: str | None,
109
+ module_name: str,
110
+ attributes: dict = {}, # noqa: B006
111
+ request: Request | None = None, # noqa: B006
112
+ ) -> Optional[WalkerArchetype]:
113
+ """Spawn walker with proper context handling and thread safety"""
114
+
115
+ if not all([walker_name, module_name]):
116
+ self.logger.error("Missing required parameters for spawning walker")
117
+ return None
118
+
119
+ ctx = self.get_context(request)
120
+ if not ctx:
121
+ return None
122
+
123
+ try:
124
+ if module_name not in JacMachine.list_modules():
125
+ self.logger.error(f"Module {module_name} not loaded")
126
+ return None
127
+
128
+ entry_node = ctx.entry_node.archetype
129
+
130
+ return JacPlugin.spawn(
131
+ JacMachine.spawn_walker(walker_name, attributes, module_name),
132
+ entry_node,
133
+ )
134
+ except Exception as e:
135
+ self.logger.error(f"Error spawning walker: {e}\n{traceback.format_exc()}")
136
+ return None
137
+ finally:
138
+ if ctx:
139
+ ctx.close()
140
+ if JASECI_CONTEXT.get(None) == ctx:
141
+ JASECI_CONTEXT.set(None)
142
+
143
+ def _authenticate(self) -> None:
144
+ """Thread-safe authentication with retry logic and improved error handling"""
145
+ user = os.environ.get("JIVAS_USER")
146
+ password = os.environ.get("JIVAS_PASSWORD")
147
+ if not user or not password:
148
+ self.logger.error("Missing JIVAS_USER or JIVAS_PASSWORD")
149
+ return
150
+
151
+ login_url = f"http://{self.host}:{self.port}/user/login"
152
+ register_url = f"http://{self.host}:{self.port}/user/register"
153
+
154
+ with self._lock:
155
+ try:
156
+ # Try login first
157
+ response = requests.post(
158
+ login_url, json={"email": user, "password": password}, timeout=15
159
+ )
160
+ self.logger.info(f"Login response status: {response.status_code}")
161
+ if response.status_code == 200:
162
+ self._process_auth_response(response.json())
163
+ return
164
+
165
+ # Register if login fails
166
+ reg_response = requests.post(
167
+ register_url, json={"email": user, "password": password}, timeout=15
168
+ )
169
+ self.logger.info(
170
+ f"Register response status: {reg_response.status_code}"
171
+ )
172
+ if reg_response.status_code == 201:
173
+ # Retry login after registration
174
+ login_response = requests.post(
175
+ login_url,
176
+ json={"email": user, "password": password},
177
+ timeout=15,
178
+ )
179
+ self.logger.info(
180
+ f"Retry login response status: {login_response.status_code}"
181
+ )
182
+ if login_response.status_code == 200:
183
+ self._process_auth_response(login_response.json())
184
+ except requests.exceptions.RequestException as e:
185
+ self.logger.error(f"Network error during authentication: {e}")
186
+ except Exception as e:
187
+ self.logger.error(
188
+ f"Authentication failed: {e}\n{traceback.format_exc()}"
189
+ )
190
+ self.reset()
191
+
192
+ def _process_auth_response(self, response_data: dict) -> None:
193
+ """Process authentication response and update state"""
194
+ user_data = response_data.get("user", {})
195
+ root_id = user_data.get("root_id", "")
196
+ token = response_data.get("token", "")
197
+ expiration = user_data.get("expiration")
198
+
199
+ if not all([root_id, token, expiration]):
200
+ self.logger.error("Invalid authentication response")
201
+ return
202
+
203
+ self.update(root_id, token, expiration)
204
+
205
+ # Async versions of methods
206
+ async def _authenticate_async(self) -> None:
207
+ """Asynchronous wrapper for authentication"""
208
+ loop = asyncio.get_running_loop()
209
+ await loop.run_in_executor(None, self._authenticate)
210
+
211
+ async def spawn_walker_async(
212
+ self,
213
+ walker_name: str,
214
+ module_name: str,
215
+ attributes: dict,
216
+ request: Request | None = None,
217
+ ) -> Optional[WalkerArchetype]:
218
+ """Asynchronous wrapper for walker spawning"""
219
+ loop = asyncio.get_running_loop()
220
+ return await loop.run_in_executor(
221
+ None, self.spawn_walker, walker_name, module_name, attributes, request
222
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jvserve
3
- Version: 2.0.16
3
+ Version: 2.1.2
4
4
  Summary: FastAPI webserver for loading and interaction with JIVAS agents.
5
5
  Home-page: https://github.com/TrueSelph/jvserve
6
6
  Author: TrueSelph Inc.
@@ -9,7 +9,8 @@ Keywords: jivas
9
9
  Requires-Python: >=3.12.0
10
10
  Description-Content-Type: text/markdown
11
11
  License-File: LICENSE
12
- Requires-Dist: jac-cloud==0.1.20
12
+ Requires-Dist: jac-cloud
13
+ Requires-Dist: jaclang==0.8.4
13
14
  Requires-Dist: pyaml>=25.1.0
14
15
  Requires-Dist: requests>=2.32.3
15
16
  Requires-Dist: aiohttp>=3.10.10
@@ -80,7 +81,7 @@ jac jvfileserve ./static
80
81
  ### Supported Arguments
81
82
 
82
83
  - **filename**: Path to your JAC file.
83
- - **host**: Host address to bind the server (default: `0.0.0.0`).
84
+ - **host**: Host address to bind the server (default: `localhost`).
84
85
  - **port**: Port number to bind the server (default: `8000`).
85
86
  - **loglevel**: Logging level (default: `INFO`).
86
87
  - **workers**: Number of worker processes (optional).
@@ -11,8 +11,8 @@ jvserve.egg-info/requires.txt
11
11
  jvserve.egg-info/top_level.txt
12
12
  jvserve/lib/__init__.py
13
13
  jvserve/lib/agent_interface.py
14
- jvserve/lib/agent_pulse.py
15
14
  jvserve/lib/file_interface.py
15
+ jvserve/lib/jac_interface.py
16
16
  jvserve/lib/jvlogger.py
17
17
  tests/test_file_interface.py
18
18
  tests/test_jvlogger.py
@@ -1,4 +1,5 @@
1
- jac-cloud==0.1.20
1
+ jac-cloud
2
+ jaclang==0.8.4
2
3
  pyaml>=25.1.0
3
4
  requests>=2.32.3
4
5
  aiohttp>=3.10.10