jvserve 2.1.17__tar.gz → 2.1.19__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.1.17
3
+ Version: 2.1.19
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.
@@ -1,6 +1,7 @@
1
1
  """Module for registering CLI plugins for jaseci."""
2
2
 
3
3
  import asyncio
4
+ import json
4
5
  import logging
5
6
  import mimetypes
6
7
  import os
@@ -9,6 +10,7 @@ import threading
9
10
  import time
10
11
  from concurrent.futures import ThreadPoolExecutor
11
12
  from contextlib import asynccontextmanager
13
+ from datetime import datetime
12
14
  from pickle import load
13
15
  from typing import AsyncIterator, Optional
14
16
 
@@ -18,10 +20,11 @@ import pymongo
18
20
  import requests
19
21
  from bson import ObjectId
20
22
  from dotenv import load_dotenv
21
- from fastapi import FastAPI, HTTPException, Response
23
+ from fastapi import FastAPI, HTTPException, Request, Response
22
24
  from fastapi.middleware.cors import CORSMiddleware
23
- from fastapi.responses import FileResponse, StreamingResponse
25
+ from fastapi.responses import FileResponse, JSONResponse, StreamingResponse
24
26
  from jac_cloud.core.context import JaseciContext
27
+ from jac_cloud.jaseci.datasources.redis import Redis
25
28
  from jac_cloud.jaseci.main import FastAPI as JaseciFastAPI # type: ignore
26
29
  from jac_cloud.jaseci.utils import logger
27
30
  from jac_cloud.jaseci.utils.logger import Level
@@ -41,6 +44,8 @@ from jvserve.lib.file_interface import (
41
44
  )
42
45
  from jvserve.lib.jvlogger import JVLogger
43
46
 
47
+ redis = Redis().get_rd()
48
+
44
49
  load_dotenv(".env")
45
50
  # quiet the jac_cloud logger down to errors only
46
51
  logger.setLevel(Level.ERROR.value)
@@ -56,6 +61,10 @@ collection_init_lock = asyncio.Lock()
56
61
  watcher_enabled = True
57
62
 
58
63
 
64
+ # taken from kubernetes HOSTNAME for replicated deployment
65
+ SERVER_ID = os.environ.get("HOSTNAME", "unknown_server")
66
+
67
+
59
68
  async def get_url_proxy_collection() -> pymongo.collection.Collection:
60
69
  """Thread-safe initialization of MongoDB collection"""
61
70
  global url_proxy_collection
@@ -244,6 +253,42 @@ def run_jivas(filename: str, host: str = "localhost", port: int = 8000) -> None:
244
253
  allow_headers=["*"],
245
254
  )
246
255
 
256
+ @app.get("/action/webhook/{namespace}/{action}/{walker}/{agent_id}/{key}")
257
+ async def webhook_exec_get(
258
+ namespace: str,
259
+ action: str,
260
+ walker: str,
261
+ agent_id: str,
262
+ key: str,
263
+ request: Request,
264
+ ) -> JSONResponse:
265
+ return await agent_interface.webhook_exec(
266
+ namespace=namespace,
267
+ action=action,
268
+ walker=walker,
269
+ agent_id=agent_id,
270
+ key=key,
271
+ request=request,
272
+ )
273
+
274
+ @app.post("/action/webhook/{namespace}/{action}/{walker}/{agent_id}/{key}")
275
+ async def webhook_exec_post(
276
+ namespace: str,
277
+ action: str,
278
+ walker: str,
279
+ agent_id: str,
280
+ key: str,
281
+ request: Request,
282
+ ) -> JSONResponse:
283
+ return await agent_interface.webhook_exec(
284
+ namespace=namespace,
285
+ action=action,
286
+ walker=walker,
287
+ agent_id=agent_id,
288
+ key=key,
289
+ request=request,
290
+ )
291
+
247
292
  # Ensure the local file directory exists if that's the interface
248
293
  if FILE_INTERFACE == "local":
249
294
  directory = os.environ.get("JIVAS_FILES_ROOT_PATH", DEFAULT_FILES_ROOT)
@@ -299,6 +344,8 @@ def run_jivas(filename: str, host: str = "localhost", port: int = 8000) -> None:
299
344
  jvlogger.info("Development mode: Starting file watcher")
300
345
  start_file_watcher(watchdir, filename, host, port)
301
346
 
347
+ threading.Thread(target=redis_listener, daemon=True).start()
348
+
302
349
  # Run the app
303
350
  JaseciFastAPI.start(host=host, port=port)
304
351
 
@@ -386,3 +433,63 @@ class JacCmd:
386
433
  def jvserve(filename: str, host: str = "localhost", port: int = 8000) -> None:
387
434
  """Launch unified JIVAS server with file services"""
388
435
  run_jivas(filename, host, port)
436
+
437
+
438
+ def handle_message(msg: str) -> None:
439
+ """
440
+ Handles incoming messages from the Redis channel 'jivas_actions'.
441
+ Expects the message to be a JSON string with 'action' and 'initiator' fields.
442
+ Only acts on 'reload_jivas' messages not sent by this pod.
443
+ """
444
+ try:
445
+ data = json.loads(msg)
446
+ except json.JSONDecodeError:
447
+ print(f"Received invalid message: {msg}")
448
+ return
449
+
450
+ action = data.get("action")
451
+ initiator = data.get("initiator")
452
+
453
+ # Ignore messages sent by this pod
454
+ if initiator == SERVER_ID:
455
+ jvlogger.info("Skipping message from self")
456
+ return
457
+
458
+ if action == "reload_jivas":
459
+ print(f"Received reload_jivas action from {initiator}, executing reload.")
460
+ reload_jivas()
461
+ else:
462
+ print(f"Ignored action: {action} from {initiator}")
463
+
464
+
465
+ def redis_listener() -> None:
466
+ """Listens to the Redis channel 'walker_install_action' and handles incoming messages."""
467
+ pubsub = redis.pubsub()
468
+ pubsub.subscribe("jivas_actions")
469
+ jvlogger.info("Subscribed to channel: jivas_actions")
470
+
471
+ for message in pubsub.listen():
472
+ if message["type"] == "message":
473
+ handle_message(message["data"])
474
+
475
+
476
+ def send_action_notification(action: str, extra_data: dict | None = None) -> None:
477
+ """
478
+ Sends a message to the Redis channel 'jivas_actions'.
479
+
480
+ Args:
481
+ action (str): the action name, e.g., 'reload_jivas'
482
+ extra_data (dict): optional additional data to include in the message
483
+ """
484
+ payload = {
485
+ "action": action,
486
+ "timestamp": datetime.utcnow().isoformat(),
487
+ "initiator": SERVER_ID,
488
+ }
489
+
490
+ if extra_data:
491
+ payload.update(extra_data)
492
+
493
+ # Publish the message
494
+ redis.publish("jivas_actions", json.dumps(payload))
495
+ jvlogger.info(f"Sent message: {payload}")
@@ -6,6 +6,8 @@ import traceback
6
6
  from typing import Any
7
7
 
8
8
  import requests
9
+ from fastapi import Request
10
+ from fastapi.responses import JSONResponse
9
11
 
10
12
  from jvserve.lib.jac_interface import JacInterface
11
13
 
@@ -47,6 +49,61 @@ class AgentInterface:
47
49
  self._jac.reset()
48
50
  self.logger.error(f"Init error: {e}\n{traceback.format_exc()}")
49
51
 
52
+ async def webhook_exec(
53
+ self,
54
+ agent_id: str,
55
+ key: str,
56
+ namespace: str,
57
+ action: str,
58
+ walker: str,
59
+ request: Request,
60
+ ) -> JSONResponse:
61
+ """Trigger webhook execution - async compatible"""
62
+ try:
63
+
64
+ if not self._jac.is_valid():
65
+ self.logger.warning(
66
+ "Invalid API state for webhook, attempting to reinstate it..."
67
+ )
68
+ self._jac._authenticate()
69
+
70
+ header = dict(request.headers)
71
+ try:
72
+ payload = await request.json()
73
+ if not payload:
74
+ payload = {}
75
+ except Exception:
76
+ payload = {}
77
+
78
+ walker_obj = await self._jac.spawn_walker_async(
79
+ walker_name=walker,
80
+ module_name=f"actions.{namespace}.{action}.{walker}",
81
+ attributes={
82
+ "agent_id": agent_id,
83
+ "key": key,
84
+ "header": header,
85
+ "payload": payload,
86
+ },
87
+ )
88
+ if not walker_obj:
89
+ self.logger.error("Webhook execution failed")
90
+ return JSONResponse(
91
+ content={"error": "Webhook execution failed"}, status_code=500
92
+ )
93
+
94
+ result = walker_obj.response
95
+ return JSONResponse(
96
+ status_code=result.get("status", 200),
97
+ content=result.get("message", "200 OK"),
98
+ )
99
+
100
+ except Exception as e:
101
+ self._jac.reset()
102
+ self.logger.error(f"Webhook callback error: {e}\n{traceback.format_exc()}")
103
+ return JSONResponse(
104
+ content={"error": "Internal server error"}, status_code=500
105
+ )
106
+
50
107
  def api_pulse(self, action_label: str, agent_id: str) -> dict:
51
108
  """Synchronous pulse API call"""
52
109
  if not self._jac.is_valid():
@@ -124,7 +124,7 @@ class JacInterface:
124
124
 
125
125
  try:
126
126
  if module_name not in JacMachine.list_modules():
127
- self.logger.error(f"Module {module_name} not loaded")
127
+ self.logger.error(f"Module {module_name} not found")
128
128
  return None
129
129
 
130
130
  entry_node = ctx.entry_node.archetype
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jvserve
3
- Version: 2.1.17
3
+ Version: 2.1.19
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.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes