cycls 0.0.2.65__py3-none-any.whl → 0.0.2.66__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.
cycls/auth.py ADDED
@@ -0,0 +1,4 @@
1
+ JWKS_PROD = "https://clerk.cycls.ai/.well-known/jwks.json"
2
+ JWKS_TEST = "https://select-sloth-58.clerk.accounts.dev/.well-known/jwks.json"
3
+ PK_LIVE = "pk_live_Y2xlcmsuY3ljbHMuYWkk"
4
+ PK_TEST = "pk_test_c2VsZWN0LXNsb3RoLTU4LmNsZXJrLmFjY291bnRzLmRldiQ"
cycls/sdk.py CHANGED
@@ -1,11 +1,25 @@
1
1
  import json, time, modal, inspect, uvicorn
2
2
  from .runtime import Runtime
3
3
  from modal.runner import run_app
4
- from .web import web
4
+ from .web import web, Config
5
+ from .auth import PK_LIVE, PK_TEST, JWKS_PROD, JWKS_TEST
5
6
  import importlib.resources
7
+ from pydantic import BaseModel
8
+ from typing import Callable
6
9
 
7
10
  CYCLS_PATH = importlib.resources.files('cycls')
8
11
 
12
+ class RegisteredAgent(BaseModel):
13
+ func: Callable
14
+ name: str
15
+ domain: str
16
+ config: Config
17
+
18
+ def set_prod(config: Config, prod: bool):
19
+ config.prod = prod
20
+ config.pk = PK_LIVE if prod else PK_TEST
21
+ config.jwks = JWKS_PROD if prod else JWKS_TEST
22
+
9
23
  themes = {
10
24
  "default": CYCLS_PATH.joinpath('default-theme'),
11
25
  "dev": CYCLS_PATH.joinpath('dev-theme'),
@@ -43,13 +57,21 @@ class Agent:
43
57
  auth=True
44
58
  analytics=True
45
59
  def decorator(f):
46
- self.registered_functions.append({
47
- "func": f,
48
- "config": ["theme", False, self.org, self.api_token, header, intro, title, auth, tier, analytics],
49
- # "name": name,
50
- "name": name or (f.__name__).replace('_', '-'),
51
- "domain": domain or f"{name}.cycls.ai",
52
- })
60
+ agent_name = name or f.__name__.replace('_', '-')
61
+ self.registered_functions.append(RegisteredAgent(
62
+ func=f,
63
+ name=agent_name,
64
+ domain=domain or f"{agent_name}.cycls.ai",
65
+ config=Config(
66
+ header=header,
67
+ intro=intro,
68
+ title=title,
69
+ auth=auth,
70
+ tier=tier,
71
+ analytics=analytics,
72
+ org=self.org,
73
+ ),
74
+ ))
53
75
  return f
54
76
  return decorator
55
77
 
@@ -57,13 +79,14 @@ class Agent:
57
79
  if not self.registered_functions:
58
80
  print("Error: No @agent decorated function found.")
59
81
  return
60
-
61
- i = self.registered_functions[0]
82
+
83
+ agent = self.registered_functions[0]
62
84
  if len(self.registered_functions) > 1:
63
- print(f"⚠️ Warning: Multiple agents found. Running '{i['name']}'.")
85
+ print(f"⚠️ Warning: Multiple agents found. Running '{agent.name}'.")
64
86
  print(f"🚀 Starting local server at localhost:{port}")
65
- i["config"][0] = self.theme
66
- uvicorn.run(web(i["func"], *i["config"]), host="0.0.0.0", port=port)
87
+ agent.config.public_path = self.theme
88
+ set_prod(agent.config, False)
89
+ uvicorn.run(web(agent.func, agent.config), host="0.0.0.0", port=port)
67
90
  return
68
91
 
69
92
  def deploy(self, prod=False, port=8080):
@@ -74,27 +97,29 @@ class Agent:
74
97
  print("🛑 Error: Please add your Cycls API key")
75
98
  return
76
99
 
77
- i = self.registered_functions[0]
100
+ agent = self.registered_functions[0]
78
101
  if len(self.registered_functions) > 1:
79
- print(f"⚠️ Warning: Multiple agents found. Running '{i['name']}'.")
102
+ print(f"⚠️ Warning: Multiple agents found. Running '{agent.name}'.")
80
103
 
81
- # i["config"][1] = False
82
- i["config"][1] = prod
104
+ set_prod(agent.config, prod)
105
+ func = agent.func
106
+ name = agent.name
107
+ config_dict = agent.config.model_dump()
83
108
 
84
- copy={str(self.theme):"theme", str(CYCLS_PATH)+"/web.py":"web.py"}
85
- copy.update({i:i for i in self.copy})
86
- copy.update({i:f"public/{i}" for i in self.copy_public})
109
+ files = {str(self.theme): "theme", str(CYCLS_PATH)+"/web.py": "web.py"}
110
+ files.update({f: f for f in self.copy})
111
+ files.update({f: f"public/{f}" for f in self.copy_public})
87
112
 
88
113
  new = Runtime(
89
- func=lambda port: __import__("web").serve(i["func"], i["config"], i["name"], port),
90
- name=i["name"],
114
+ func=lambda port: __import__("web").serve(func, config_dict, name, port),
115
+ name=name,
91
116
  apt_packages=self.apt,
92
117
  pip_packages=["fastapi[standard]", "pyjwt", "cryptography", "uvicorn", *self.pip],
93
- copy=copy,
118
+ copy=files,
94
119
  base_url=self.base_url,
95
120
  api_key=self.key
96
121
  )
97
- new.deploy(port=port) if prod else new.run(port=port)
122
+ new.deploy(port=port) if prod else new.run(port=port)
98
123
  return
99
124
 
100
125
  def modal(self, prod=False):
@@ -117,22 +142,26 @@ class Agent:
117
142
  print("Error: No @agent decorated function found.")
118
143
  return
119
144
 
120
- for i in self.registered_functions:
121
- i["config"][1] = True if prod else False
122
- self.app.function(serialized=True, name=i["name"])(
123
- modal.asgi_app(label=i["name"], custom_domains=[i["domain"]])
124
- (lambda: __import__("web").web(i["func"], *i["config"]))
145
+ for agent in self.registered_functions:
146
+ set_prod(agent.config, prod)
147
+ func = agent.func
148
+ name = agent.name
149
+ domain = agent.domain
150
+ config_dict = agent.config.model_dump()
151
+ self.app.function(serialized=True, name=name)(
152
+ modal.asgi_app(label=name, custom_domains=[domain])
153
+ (lambda: __import__("web").web(func, config_dict))
125
154
  )
126
155
  if prod:
127
- for i in self.registered_functions:
128
- print(f"✅ Deployed to ⇒ https://{i['domain']}")
129
- self.app.deploy(client=self.client, name=self.registered_functions[0]["name"])
156
+ for agent in self.registered_functions:
157
+ print(f"✅ Deployed to ⇒ https://{agent.domain}")
158
+ self.app.deploy(client=self.client, name=self.registered_functions[0].name)
130
159
  return
131
160
  else:
132
161
  with modal.enable_output():
133
162
  run_app(app=self.app, client=self.client)
134
163
  print(" Modal development server is running. Press Ctrl+C to stop.")
135
- with modal.enable_output(), run_app(app=self.app, client=self.client):
164
+ with modal.enable_output(), run_app(app=self.app, client=self.client):
136
165
  while True: time.sleep(10)
137
166
 
138
167
  # docker system prune -af
cycls/web.py CHANGED
@@ -1,10 +1,20 @@
1
1
  import json, inspect
2
2
  from pathlib import Path
3
-
4
- JWKS_PROD = "https://clerk.cycls.ai/.well-known/jwks.json"
5
- PK_LIVE = "pk_live_Y2xlcmsuY3ljbHMuYWkk"
6
- JWKS_TEST = "https://select-sloth-58.clerk.accounts.dev/.well-known/jwks.json"
7
- PK_TEST = "pk_test_c2VsZWN0LXNsb3RoLTU4LmNsZXJrLmFjY291bnRzLmRldiQ"
3
+ from pydantic import BaseModel
4
+ from typing import Optional
5
+
6
+ class Config(BaseModel):
7
+ public_path: str = "theme"
8
+ header: str = ""
9
+ intro: str = ""
10
+ title: str = ""
11
+ prod: bool = False
12
+ auth: bool = False
13
+ tier: str = "free"
14
+ analytics: bool = False
15
+ org: Optional[str] = None
16
+ pk: str = ""
17
+ jwks: str = ""
8
18
 
9
19
  async def openai_encoder(stream):
10
20
  if inspect.isasyncgen(stream):
@@ -48,17 +58,20 @@ class Messages(list):
48
58
  def raw(self):
49
59
  return self._raw
50
60
 
51
- def web(func, public_path="", prod=False, org=None, api_token=None, header="", intro="", title="", auth=False, tier="", analytics=False): # API auth
61
+ def web(func, config):
52
62
  from fastapi import FastAPI, Request, HTTPException, status, Depends
53
- from fastapi.responses import StreamingResponse , HTMLResponse
63
+ from fastapi.responses import StreamingResponse
54
64
  from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
55
65
  import jwt
56
66
  from jwt import PyJWKClient
57
- from pydantic import BaseModel, EmailStr
67
+ from pydantic import EmailStr
58
68
  from typing import List, Optional, Any
59
69
  from fastapi.staticfiles import StaticFiles
60
70
 
61
- jwks = PyJWKClient(JWKS_PROD if prod else JWKS_TEST)
71
+ if isinstance(config, dict):
72
+ config = Config(**config)
73
+
74
+ jwks = PyJWKClient(config.jwks)
62
75
 
63
76
  class User(BaseModel):
64
77
  id: str
@@ -67,18 +80,6 @@ def web(func, public_path="", prod=False, org=None, api_token=None, header="", i
67
80
  org: Optional[str] = None
68
81
  plans: List[str] = []
69
82
 
70
- class Metadata(BaseModel):
71
- header: str
72
- intro: str
73
- title: str
74
- prod: bool
75
- auth: bool
76
- tier: str
77
- analytics: bool
78
- org: Optional[str]
79
- pk_live: str
80
- pk_test: str
81
-
82
83
  class Context(BaseModel):
83
84
  messages: Any
84
85
  user: Optional[User] = None
@@ -99,7 +100,7 @@ def web(func, public_path="", prod=False, org=None, api_token=None, header="", i
99
100
  @app.post("/")
100
101
  @app.post("/chat/cycls")
101
102
  @app.post("/chat/completions")
102
- async def back(request: Request, jwt: Optional[dict] = Depends(validate) if auth else None):
103
+ async def back(request: Request, jwt: Optional[dict] = Depends(validate) if config.auth else None):
103
104
  data = await request.json()
104
105
  messages = data.get("messages")
105
106
  user_data = jwt.get("user") if jwt else None
@@ -111,29 +112,20 @@ def web(func, public_path="", prod=False, org=None, api_token=None, header="", i
111
112
  stream = encoder(stream)
112
113
  return StreamingResponse(stream, media_type="text/event-stream")
113
114
 
114
- @app.get("/metadata")
115
- async def metadata():
116
- return Metadata(
117
- header=header,
118
- intro=intro,
119
- title=title,
120
- prod=prod,
121
- auth=auth,
122
- tier=tier,
123
- analytics=analytics,
124
- org=org,
125
- pk_live=PK_LIVE,
126
- pk_test=PK_TEST
127
- )
115
+ @app.get("/config")
116
+ async def get_config():
117
+ return config
128
118
 
129
119
  if Path("public").is_dir():
130
120
  app.mount("/public", StaticFiles(directory="public", html=True))
131
- app.mount("/", StaticFiles(directory=public_path, html=True))
121
+ app.mount("/", StaticFiles(directory=config.public_path, html=True))
132
122
 
133
123
  return app
134
124
 
135
125
  def serve(func, config, name, port):
136
126
  import uvicorn, logging
127
+ if isinstance(config, dict):
128
+ config = Config(**config)
137
129
  logging.getLogger("uvicorn.error").addFilter(lambda r: "0.0.0.0" not in r.getMessage())
138
130
  print(f"\n🔨 {name} => http://localhost:{port}\n")
139
- uvicorn.run(web(func, *config), host="0.0.0.0", port=port)
131
+ uvicorn.run(web(func, config), host="0.0.0.0", port=port)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cycls
3
- Version: 0.0.2.65
3
+ Version: 0.0.2.66
4
4
  Summary: Distribute Intelligence
5
5
  Author: Mohammed J. AlRujayi
6
6
  Author-email: mj@cycls.com
@@ -1,11 +1,12 @@
1
1
  cycls/__init__.py,sha256=bVT0dYTXLdSC3ZURgtm-DEOj-VO6RUM6zGsJB0zuj6Y,61
2
+ cycls/auth.py,sha256=xkndHZyCfnlertMMEKerCJjf23N3fVcTRVTTSXTTuzg,247
2
3
  cycls/default-theme/assets/index-B0ZKcm_V.css,sha256=wK9-NhEB8xPcN9Zv69zpOcfGTlFbMwyC9WqTmSKUaKw,6546
3
4
  cycls/default-theme/assets/index-D5EDcI4J.js,sha256=sN4qRcAXa7DBd9JzmVcCoCwH4l8cNCM-U9QGUjBvWSo,1346506
4
5
  cycls/default-theme/index.html,sha256=bM-yW_g0cGrV40Q5yY3ccY0fM4zI1Wuu5I8EtGFJIxs,828
5
6
  cycls/dev-theme/index.html,sha256=QJBHkdNuMMiwQU7o8dN8__8YQeQB45D37D-NCXIWB2Q,11585
6
7
  cycls/runtime.py,sha256=hLBtwtGz0FCW1-EPCJy6kMdF2fB3i6Df_H8-bm7qeK0,18223
7
- cycls/sdk.py,sha256=9R8UYaYi44DsZyO-Nxrv-g0y5BJ1IR_4PNyzvRfIbSs,5915
8
- cycls/web.py,sha256=_GU50yjfD7podThnivNJ0zv6mPhFLTh25xVwTwTdhXQ,5101
9
- cycls-0.0.2.65.dist-info/METADATA,sha256=HNFesDo57HQ1bFJVVbdfhTSr0eckZZjF97-mAUofAnc,7943
10
- cycls-0.0.2.65.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
11
- cycls-0.0.2.65.dist-info/RECORD,,
8
+ cycls/sdk.py,sha256=jT6t2btthVI8Bdp0-BttS4apaH0k7GCobUHxE9BTUj4,6728
9
+ cycls/web.py,sha256=3M3qaWTNY3dpgd7Vq5aXREp-cIFsHrDqBQ1YkGrOaUk,4659
10
+ cycls-0.0.2.66.dist-info/METADATA,sha256=g9ehLjsx8yYm6muJ_16lNlCzPtKchy2LlTjFP5nwzHk,7943
11
+ cycls-0.0.2.66.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
12
+ cycls-0.0.2.66.dist-info/RECORD,,