aicosmos-client 0.0.5__py3-none-any.whl → 0.0.6__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.
- aicosmos_client/client.py +141 -95
- {aicosmos_client-0.0.5.dist-info → aicosmos_client-0.0.6.dist-info}/METADATA +6 -2
- aicosmos_client-0.0.6.dist-info/RECORD +7 -0
- aicosmos_client-0.0.5.dist-info/RECORD +0 -7
- {aicosmos_client-0.0.5.dist-info → aicosmos_client-0.0.6.dist-info}/WHEEL +0 -0
- {aicosmos_client-0.0.5.dist-info → aicosmos_client-0.0.6.dist-info}/licenses/LICENSE +0 -0
aicosmos_client/client.py
CHANGED
@@ -1,16 +1,91 @@
|
|
1
|
+
import os
|
2
|
+
import socket
|
3
|
+
import ssl
|
4
|
+
from urllib.parse import urlparse
|
5
|
+
|
6
|
+
import certifi
|
1
7
|
import requests
|
8
|
+
from requests.adapters import HTTPAdapter
|
9
|
+
|
10
|
+
|
11
|
+
class SSLAdapter(HTTPAdapter):
|
12
|
+
"""HTTPS adapter that allows dynamic CA bundle injection."""
|
13
|
+
|
14
|
+
def __init__(self, cafile=None, *args, **kwargs):
|
15
|
+
self.cafile = cafile
|
16
|
+
super().__init__(*args, **kwargs)
|
17
|
+
|
18
|
+
def init_poolmanager(self, *args, **kwargs):
|
19
|
+
context = ssl.create_default_context()
|
20
|
+
context.load_verify_locations(cafile=certifi.where())
|
21
|
+
if self.cafile and os.path.exists(self.cafile):
|
22
|
+
context.load_verify_locations(cafile=self.cafile)
|
23
|
+
kwargs["ssl_context"] = context
|
24
|
+
return super().init_poolmanager(*args, **kwargs)
|
2
25
|
|
3
26
|
|
4
27
|
class AICosmosClient:
|
5
|
-
def __init__(
|
6
|
-
self
|
7
|
-
|
8
|
-
|
9
|
-
|
28
|
+
def __init__(
|
29
|
+
self,
|
30
|
+
base_url: str,
|
31
|
+
username: str,
|
32
|
+
password: str,
|
33
|
+
certs_dir: str = None,
|
34
|
+
auto_trust: bool = False,
|
35
|
+
):
|
36
|
+
"""
|
37
|
+
:param base_url: API base URL, e.g. 'https://aicosmos.ai/api'
|
38
|
+
:param username: Username for login
|
39
|
+
:param password: Password for login
|
40
|
+
:param certs_dir: Directory for storing trusted self-signed certs
|
41
|
+
:param auto_trust: If True, will automatically trust self-signed certs
|
42
|
+
"""
|
43
|
+
self.base_url = base_url.rstrip("/")
|
44
|
+
self.username = username
|
45
|
+
self.password = password
|
10
46
|
self.access_token: str = None
|
47
|
+
self.auto_trust = auto_trust
|
48
|
+
|
49
|
+
host = urlparse(self.base_url).hostname
|
50
|
+
self.certs_dir = certs_dir or os.path.join(
|
51
|
+
os.path.expanduser("~"), ".aicosmos", "certs"
|
52
|
+
)
|
53
|
+
os.makedirs(self.certs_dir, exist_ok=True)
|
54
|
+
self.cert_file = os.path.join(self.certs_dir, f"{host}.pem")
|
55
|
+
|
56
|
+
self.session = requests.Session()
|
57
|
+
self.session.mount("https://", SSLAdapter(cafile=self.cert_file))
|
11
58
|
|
12
59
|
self._login()
|
13
60
|
|
61
|
+
def _fetch_server_cert(self, hostname, port=443):
|
62
|
+
"""Download server's SSL certificate and save locally."""
|
63
|
+
pem_path = self.cert_file
|
64
|
+
conn = socket.create_connection((hostname, port))
|
65
|
+
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
66
|
+
context.check_hostname = False
|
67
|
+
context.verify_mode = ssl.CERT_NONE
|
68
|
+
with context.wrap_socket(conn, server_hostname=hostname) as sock:
|
69
|
+
der_cert = sock.getpeercert(True)
|
70
|
+
pem_cert = ssl.DER_cert_to_PEM_cert(der_cert)
|
71
|
+
with open(pem_path, "w") as f:
|
72
|
+
f.write(pem_cert)
|
73
|
+
return pem_path
|
74
|
+
|
75
|
+
def _robust_request(self, method, url, **kwargs):
|
76
|
+
try:
|
77
|
+
return self.session.request(method, url, **kwargs)
|
78
|
+
except requests.exceptions.SSLError as e:
|
79
|
+
if not self.auto_trust:
|
80
|
+
raise RuntimeError(
|
81
|
+
f"SSL verification failed for {url}. "
|
82
|
+
f"Set auto_trust=True to accept and store the server's certificate."
|
83
|
+
) from e
|
84
|
+
host = urlparse(self.base_url).hostname
|
85
|
+
self._fetch_server_cert(host)
|
86
|
+
self.session.mount("https://", SSLAdapter(cafile=self.cert_file))
|
87
|
+
return self.session.request(method, url, **kwargs)
|
88
|
+
|
14
89
|
def _login(self):
|
15
90
|
login_data = {
|
16
91
|
"username": self.username,
|
@@ -18,18 +93,14 @@ class AICosmosClient:
|
|
18
93
|
"grant_type": "password",
|
19
94
|
}
|
20
95
|
headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
else:
|
30
|
-
raise ValueError(f"Status code: {response.status_code}")
|
31
|
-
except Exception as e:
|
32
|
-
raise ValueError(f"Error: {e}")
|
96
|
+
response = self._robust_request(
|
97
|
+
"POST", f"{self.base_url}/user/login", data=login_data, headers=headers
|
98
|
+
)
|
99
|
+
if response.status_code == 200:
|
100
|
+
token_data = response.json()
|
101
|
+
self.access_token = token_data["access_token"]
|
102
|
+
else:
|
103
|
+
raise ValueError(f"Login failed: {response.status_code} {response.text}")
|
33
104
|
|
34
105
|
def _get_auth_headers(self):
|
35
106
|
if not self.access_token:
|
@@ -40,93 +111,68 @@ class AICosmosClient:
|
|
40
111
|
}
|
41
112
|
|
42
113
|
def _get_session_status(self, session_id):
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
raise ValueError(f"Status code: {response.status_code}")
|
53
|
-
except Exception as e:
|
54
|
-
raise ValueError(f"Error: {e}")
|
114
|
+
response = self._robust_request(
|
115
|
+
"GET",
|
116
|
+
f"{self.base_url}/sessions/{session_id}/status",
|
117
|
+
headers=self._get_auth_headers(),
|
118
|
+
)
|
119
|
+
if response.status_code == 200:
|
120
|
+
return response.json()
|
121
|
+
else:
|
122
|
+
raise ValueError(f"Status code: {response.status_code}")
|
55
123
|
|
56
124
|
def create_session(self):
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
else:
|
67
|
-
raise ValueError(f"Status code: {response.status_code}")
|
68
|
-
except Exception as e:
|
69
|
-
raise ValueError(f"Error: {e}")
|
125
|
+
response = self._robust_request(
|
126
|
+
"POST",
|
127
|
+
f"{self.base_url}/sessions/create",
|
128
|
+
headers=self._get_auth_headers(),
|
129
|
+
)
|
130
|
+
if response.status_code == 200:
|
131
|
+
return response.json()["session_id"]
|
132
|
+
else:
|
133
|
+
raise ValueError(f"Status code: {response.status_code}")
|
70
134
|
|
71
135
|
def delete_session(self, session_id: str):
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
)
|
79
|
-
if response.status_code == 200:
|
80
|
-
return
|
81
|
-
else:
|
82
|
-
raise ValueError(f"Status code: {response.status_code}")
|
83
|
-
except Exception as e:
|
84
|
-
raise ValueError(f"Error: {e}")
|
136
|
+
response = self._robust_request(
|
137
|
+
"DELETE",
|
138
|
+
f"{self.base_url}/sessions/{session_id}",
|
139
|
+
headers=self._get_auth_headers(),
|
140
|
+
)
|
141
|
+
if response.status_code != 200:
|
142
|
+
raise ValueError(f"Status code: {response.status_code}")
|
85
143
|
|
86
144
|
def get_my_sessions(self):
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
)
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
else:
|
105
|
-
raise ValueError(f"Status code: {response.status_code}")
|
106
|
-
except Exception as e:
|
107
|
-
raise ValueError(f"Error: {e}")
|
145
|
+
response = self._robust_request(
|
146
|
+
"GET",
|
147
|
+
f"{self.base_url}/sessions/my_sessions",
|
148
|
+
headers=self._get_auth_headers(),
|
149
|
+
)
|
150
|
+
if response.status_code == 200:
|
151
|
+
sessions = response.json()
|
152
|
+
self.active_sessions = sessions
|
153
|
+
return [
|
154
|
+
{
|
155
|
+
"session_id": s["session_id"],
|
156
|
+
"title": s["environment_info"].get("title"),
|
157
|
+
}
|
158
|
+
for s in sessions
|
159
|
+
]
|
160
|
+
else:
|
161
|
+
raise ValueError(f"Status code: {response.status_code}")
|
108
162
|
|
109
163
|
def get_session_history(self, session_id: str):
|
110
164
|
session = self._get_session_status(session_id)
|
111
165
|
return session.get("conversation", [])
|
112
166
|
|
113
167
|
def chat(self, session_id: str, prompt: str):
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
"
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
)
|
126
|
-
success = response.status_code == 200
|
127
|
-
if success:
|
128
|
-
return response.json()["conversation_history"]
|
129
|
-
else:
|
130
|
-
raise ValueError(f"Status code: {response.status_code}")
|
131
|
-
except Exception as e:
|
132
|
-
raise ValueError(f"Error: {e}")
|
168
|
+
data = {"user_input": prompt, "session_id": session_id}
|
169
|
+
response = self._robust_request(
|
170
|
+
"POST",
|
171
|
+
f"{self.base_url}/chat",
|
172
|
+
json=data,
|
173
|
+
headers=self._get_auth_headers(),
|
174
|
+
)
|
175
|
+
if response.status_code == 200:
|
176
|
+
return response.json()["conversation_history"]
|
177
|
+
else:
|
178
|
+
raise ValueError(f"Status code: {response.status_code}")
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: aicosmos_client
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.6
|
4
4
|
Summary: client for AICosmos platform
|
5
5
|
Project-URL: Homepage, https://github.com/pypa/sampleproject
|
6
6
|
Project-URL: Issues, https://github.com/pypa/sampleproject/issues
|
@@ -28,7 +28,10 @@ from aicosmos_client.client import AICosmosClient
|
|
28
28
|
|
29
29
|
# login
|
30
30
|
client = AICosmosClient(
|
31
|
-
base_url="
|
31
|
+
base_url="https://aicosmos.ai/api",
|
32
|
+
username="xxx",
|
33
|
+
password="xxx",
|
34
|
+
auto_trust=True,
|
32
35
|
)
|
33
36
|
|
34
37
|
# create a new session
|
@@ -62,5 +65,6 @@ To show that the client is enough to build an application, we offer you an comma
|
|
62
65
|
```Python
|
63
66
|
from aicosmos_client.cli import AICosmosCLI
|
64
67
|
|
68
|
+
# url: https://aicosmos.ai/api
|
65
69
|
AICosmosCLI().run()
|
66
70
|
```
|
@@ -0,0 +1,7 @@
|
|
1
|
+
aicosmos_client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
aicosmos_client/cli.py,sha256=V6fewmdU2Im3NNbVw0OrVbxd1mkTGYKTdfgNKJl0CwY,13979
|
3
|
+
aicosmos_client/client.py,sha256=dn58ztTJVSOP9munQcYVB8AAhUiTsJoNnPvngQceSGc,6388
|
4
|
+
aicosmos_client-0.0.6.dist-info/METADATA,sha256=EsoT3FQDaqbmGH2bEdQCCcCAUkKQKz8e4xxH74d4Sj0,2242
|
5
|
+
aicosmos_client-0.0.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
6
|
+
aicosmos_client-0.0.6.dist-info/licenses/LICENSE,sha256=XBdpsYae127l7YQyMSVQwUUo22FPis7sMts7oBjkN_g,1056
|
7
|
+
aicosmos_client-0.0.6.dist-info/RECORD,,
|
@@ -1,7 +0,0 @@
|
|
1
|
-
aicosmos_client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
aicosmos_client/cli.py,sha256=V6fewmdU2Im3NNbVw0OrVbxd1mkTGYKTdfgNKJl0CwY,13979
|
3
|
-
aicosmos_client/client.py,sha256=akVMmyDrAqOg0JI4mv_MBIA6I3VhLwDkstJlTEwiF5w,4664
|
4
|
-
aicosmos_client-0.0.5.dist-info/METADATA,sha256=7mdmOAHYtWfyEuYOiVsJPiDT4PzPBBF3LyZ83L-rC6c,2175
|
5
|
-
aicosmos_client-0.0.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
6
|
-
aicosmos_client-0.0.5.dist-info/licenses/LICENSE,sha256=XBdpsYae127l7YQyMSVQwUUo22FPis7sMts7oBjkN_g,1056
|
7
|
-
aicosmos_client-0.0.5.dist-info/RECORD,,
|
File without changes
|
File without changes
|