epsimo-agent 0.1.0

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.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/SKILL.md +85 -0
  3. package/assets/example_asset.txt +24 -0
  4. package/epsimo/__init__.py +3 -0
  5. package/epsimo/__main__.py +4 -0
  6. package/epsimo/auth.py +143 -0
  7. package/epsimo/cli.py +586 -0
  8. package/epsimo/client.py +53 -0
  9. package/epsimo/resources/assistants.py +47 -0
  10. package/epsimo/resources/credits.py +16 -0
  11. package/epsimo/resources/db.py +31 -0
  12. package/epsimo/resources/files.py +39 -0
  13. package/epsimo/resources/projects.py +30 -0
  14. package/epsimo/resources/threads.py +83 -0
  15. package/epsimo/templates/components/AuthModal/AuthModal.module.css +39 -0
  16. package/epsimo/templates/components/AuthModal/AuthModal.tsx +138 -0
  17. package/epsimo/templates/components/BuyCredits/BuyCreditsModal.module.css +96 -0
  18. package/epsimo/templates/components/BuyCredits/BuyCreditsModal.tsx +132 -0
  19. package/epsimo/templates/components/BuyCredits/CreditsDisplay.tsx +101 -0
  20. package/epsimo/templates/components/ThreadChat/ThreadChat.module.css +551 -0
  21. package/epsimo/templates/components/ThreadChat/ThreadChat.tsx +862 -0
  22. package/epsimo/templates/components/ThreadChat/components/ToolRenderers.module.css +509 -0
  23. package/epsimo/templates/components/ThreadChat/components/ToolRenderers.tsx +322 -0
  24. package/epsimo/templates/next-mvp/app/globals.css.tmpl +20 -0
  25. package/epsimo/templates/next-mvp/app/layout.tsx.tmpl +22 -0
  26. package/epsimo/templates/next-mvp/app/page.module.css.tmpl +84 -0
  27. package/epsimo/templates/next-mvp/app/page.tsx.tmpl +43 -0
  28. package/epsimo/templates/next-mvp/epsimo.yaml.tmpl +12 -0
  29. package/epsimo/templates/next-mvp/package.json.tmpl +26 -0
  30. package/epsimo/tools/library.yaml +51 -0
  31. package/package.json +27 -0
  32. package/references/api_reference.md +34 -0
  33. package/references/virtual_db_guide.md +57 -0
  34. package/requirements.txt +2 -0
  35. package/scripts/assistant.py +165 -0
  36. package/scripts/auth.py +195 -0
  37. package/scripts/credits.py +107 -0
  38. package/scripts/debug_run.py +41 -0
  39. package/scripts/example.py +19 -0
  40. package/scripts/files.py +73 -0
  41. package/scripts/find_thread.py +55 -0
  42. package/scripts/project.py +60 -0
  43. package/scripts/run.py +75 -0
  44. package/scripts/test_all_skills.py +387 -0
  45. package/scripts/test_sdk.py +83 -0
  46. package/scripts/test_streaming.py +167 -0
  47. package/scripts/test_vdb.py +65 -0
  48. package/scripts/thread.py +77 -0
  49. package/scripts/verify_skill.py +87 -0
@@ -0,0 +1,55 @@
1
+
2
+ import requests
3
+ import json
4
+ from auth import get_token, API_BASE_URL
5
+
6
+ target_thread_id = "2b4c6325-c400-4e78-81c8-ef7781bf8b49"
7
+
8
+ def check_thread():
9
+ token = get_token()
10
+ headers = {"Authorization": f"Bearer {token}"}
11
+
12
+ print(f"Checking thread {target_thread_id} with User Token...")
13
+ try:
14
+ resp = requests.get(f"{API_BASE_URL}/threads/{target_thread_id}", headers=headers)
15
+ if resp.ok:
16
+ t = resp.json()
17
+ print(f"FOUND with User Token!")
18
+ print(json.dumps(t, indent=2))
19
+ return
20
+ else:
21
+ print(f"User Token check failed: {resp.status_code}")
22
+ except Exception as e:
23
+ print(f"Error: {e}")
24
+
25
+ # Fallback: check projects
26
+ resp = requests.get(f"{API_BASE_URL}/projects/", headers=headers)
27
+ projects = resp.json()
28
+
29
+ for p in projects:
30
+ pid = p['project_id']
31
+ pname = p['name']
32
+
33
+ # Get project token
34
+ pr = requests.get(f"{API_BASE_URL}/projects/{pid}", headers=headers)
35
+ if not pr.ok:
36
+ continue
37
+ ptoken = pr.json().get('access_token')
38
+ pheaders = {"Authorization": f"Bearer {ptoken}"}
39
+
40
+ try:
41
+ resp = requests.get(f"{API_BASE_URL}/threads/{target_thread_id}", headers=pheaders)
42
+ if resp.ok:
43
+ t = resp.json()
44
+ print(f"FOUND in Project: {pname} ({pid})")
45
+ print(json.dumps(t, indent=2))
46
+ return
47
+ else:
48
+ pass # Not in this project
49
+ except:
50
+ pass
51
+
52
+ print("Thread not found by ID.")
53
+
54
+ if __name__ == "__main__":
55
+ check_thread()
@@ -0,0 +1,60 @@
1
+
2
+ import os
3
+ import sys
4
+ import argparse
5
+ import requests
6
+ import json
7
+ from auth import get_token, API_BASE_URL
8
+
9
+ def list_projects():
10
+ token = get_token()
11
+ headers = {"Authorization": f"Bearer {token}"}
12
+ response = requests.get(f"{API_BASE_URL}/projects/", headers=headers)
13
+ response.raise_for_status()
14
+ projects = response.json()
15
+ print(json.dumps(projects, indent=2))
16
+
17
+ def create_project(name, description):
18
+ token = get_token()
19
+ headers = {"Authorization": f"Bearer {token}"}
20
+ payload = {"name": name, "description": description}
21
+ response = requests.post(f"{API_BASE_URL}/projects/", headers=headers, json=payload)
22
+ if not response.ok:
23
+ print(f"Error {response.status_code}: {response.text}")
24
+ response.raise_for_status()
25
+ print(json.dumps(response.json(), indent=2))
26
+
27
+ def get_project(project_id):
28
+ token = get_token()
29
+ headers = {"Authorization": f"Bearer {token}"}
30
+ response = requests.get(f"{API_BASE_URL}/projects/{project_id}", headers=headers)
31
+ response.raise_for_status()
32
+ print(json.dumps(response.json(), indent=2))
33
+
34
+ def main():
35
+ parser = argparse.ArgumentParser(description="Manage EpsimoAI Projects")
36
+ subparsers = parser.add_subparsers(dest="command", required=True)
37
+
38
+ # List
39
+ subparsers.add_parser("list", help="List all projects")
40
+
41
+ # Get
42
+ get_parser = subparsers.add_parser("get", help="Get project details")
43
+ get_parser.add_argument("--id", required=True, help="Project ID")
44
+
45
+ # Create
46
+ create_parser = subparsers.add_parser("create", help="Create a new project")
47
+ create_parser.add_argument("--name", required=True, help="Project name")
48
+ create_parser.add_argument("--description", help="Project description", default="My AI Project")
49
+
50
+ args = parser.parse_args()
51
+
52
+ if args.command == "list":
53
+ list_projects()
54
+ elif args.command == "get":
55
+ get_project(args.id)
56
+ elif args.command == "create":
57
+ create_project(args.name, args.description)
58
+
59
+ if __name__ == "__main__":
60
+ main()
package/scripts/run.py ADDED
@@ -0,0 +1,75 @@
1
+
2
+ import os
3
+ import sys
4
+ import argparse
5
+ import requests
6
+ import json
7
+ from auth import get_token, get_project_token, API_BASE_URL
8
+
9
+ def stream_run(project_id, thread_id, message, assistant_id=None):
10
+ token = get_token()
11
+
12
+ # Get project specific token
13
+ project_token = get_project_token(project_id)
14
+
15
+ headers = {
16
+ "Authorization": f"Bearer {project_token}",
17
+ "Accept": "text/event-stream"
18
+ }
19
+
20
+ # Matches frontend structure in ThreadsPage.handleSendMessage
21
+ payload = {
22
+ "thread_id": thread_id,
23
+ "input": [{
24
+ "content": message,
25
+ "type": "human",
26
+ "role": "user"
27
+ }],
28
+ "stream_mode": ["messages", "events", "values"]
29
+ }
30
+
31
+ # Frontend doesn't pass assistant_id here, but we can if API supports override.
32
+ # Frontend relies on thread <-> assistant link.
33
+ if assistant_id:
34
+ payload["assistant_id"] = assistant_id
35
+
36
+ response = requests.post(f"{API_BASE_URL}/runs/stream", headers=headers, json=payload, stream=True)
37
+
38
+ if not response.ok:
39
+ print(f"Status: {response.status_code}")
40
+ print(f"Body: {response.text}")
41
+
42
+ response.raise_for_status()
43
+
44
+ print("Streaming response...")
45
+ for line in response.iter_lines():
46
+ if line:
47
+ decoded_line = line.decode('utf-8')
48
+ if decoded_line.startswith("data:"):
49
+ data_str = decoded_line[5:].strip()
50
+ if data_str == "[DONE]":
51
+ break
52
+ try:
53
+ data = json.loads(data_str)
54
+ print(json.dumps(data, indent=None))
55
+ except json.JSONDecodeError:
56
+ print(f"Raw: {data_str}")
57
+
58
+ def main():
59
+ parser = argparse.ArgumentParser(description="Manage EpsimoAI Runs")
60
+ subparsers = parser.add_subparsers(dest="command", required=True)
61
+
62
+ # Stream
63
+ stream_parser = subparsers.add_parser("stream", help="Stream a run")
64
+ stream_parser.add_argument("--project-id", required=True, help="Project ID")
65
+ stream_parser.add_argument("--thread-id", required=True, help="Thread ID")
66
+ stream_parser.add_argument("--message", required=True, help="User message")
67
+ stream_parser.add_argument("--assistant-id", help="Optional Assistant ID override (default: uses thread's assistant)")
68
+
69
+ args = parser.parse_args()
70
+
71
+ if args.command == "stream":
72
+ stream_run(args.project_id, args.thread_id, args.message, args.assistant_id)
73
+
74
+ if __name__ == "__main__":
75
+ main()
@@ -0,0 +1,387 @@
1
+
2
+ import os
3
+ import sys
4
+ import json
5
+ import time
6
+ import requests
7
+ import uuid
8
+ from auth import get_token, get_project_token, API_BASE_URL
9
+
10
+ # --- Colors for Output ---
11
+ class Colors:
12
+ HEADER = '\033[95m'
13
+ OKBLUE = '\033[94m'
14
+ OKCYAN = '\033[96m'
15
+ OKGREEN = '\033[92m'
16
+ WARNING = '\033[93m'
17
+ FAIL = '\033[91m'
18
+ ENDC = '\033[0m'
19
+ BOLD = '\033[1m'
20
+ UNDERLINE = '\033[4m'
21
+
22
+ def print_pass(msg):
23
+ print(f"{Colors.OKGREEN}✅ {msg}{Colors.ENDC}")
24
+
25
+ def print_fail(msg):
26
+ print(f"{Colors.FAIL}❌ {msg}{Colors.ENDC}")
27
+
28
+ def print_info(msg):
29
+ print(f"{Colors.OKCYAN}ℹ️ {msg}{Colors.ENDC}")
30
+
31
+ def print_header(msg):
32
+ print(f"\n{Colors.HEADER}{Colors.BOLD}=== {msg} ==={Colors.ENDC}")
33
+
34
+ class SkillTester:
35
+ def __init__(self):
36
+ self.access_token = None
37
+ self.project_id = None
38
+ self.project_token = None
39
+ self.assistant_id = None
40
+ self.thread_id = None
41
+ self.file_path = "test_secret.txt"
42
+ self.secret_code = f"TEST-SECRET-{uuid.uuid4().hex[:8]}"
43
+
44
+ def step_1_auth(self):
45
+ print_header("Step 1: Authentication")
46
+ try:
47
+ self.access_token = get_token()
48
+ if not self.access_token:
49
+ raise ValueError("No token returned")
50
+ print_pass("Authentication successful")
51
+
52
+ # Check Balance (Credits)
53
+ headers = {"Authorization": f"Bearer {self.access_token}"}
54
+ resp = requests.get(f"{API_BASE_URL}/auth/thread-info", headers=headers)
55
+ if resp.ok:
56
+ data = resp.json()
57
+ print_info(f"Threads Used: {data.get('thread_counter')}/{data.get('thread_max')}")
58
+ else:
59
+ print_fail("Could not fetch balance")
60
+
61
+ except Exception as e:
62
+ print_fail(f"Auth failed: {e}")
63
+ sys.exit(1)
64
+
65
+ def step_2_projects(self):
66
+ print_header("Step 2: Project Management")
67
+
68
+ # Create
69
+ unique_name = f"AutoTest Project {int(time.time())}"
70
+ print_info(f"Creating project: {unique_name}")
71
+
72
+ headers = {"Authorization": f"Bearer {self.access_token}"}
73
+ payload = {
74
+ "name": unique_name,
75
+ "description": "Automated test project"
76
+ }
77
+
78
+ try:
79
+ resp = requests.post(f"{API_BASE_URL}/projects/", headers=headers, json=payload)
80
+ if not resp.ok:
81
+ raise ValueError(f"Project creation failed: {resp.text}")
82
+
83
+ data = resp.json()
84
+ self.project_id = data["project_id"]
85
+ print_pass(f"Created project {self.project_id}")
86
+
87
+ # Get Project Token
88
+ self.project_token = get_project_token(self.project_id)
89
+ print_pass("Retrieved project-specific token")
90
+
91
+ except Exception as e:
92
+ print_fail(f"Project step failed: {e}")
93
+ sys.exit(1)
94
+
95
+ def step_3_assistants(self):
96
+ print_header("Step 3: Assistant Management")
97
+
98
+ headers = {"Authorization": f"Bearer {self.project_token}"}
99
+
100
+ # Create
101
+ name = f"AutoTest Assistant {uuid.uuid4().hex[:4]}"
102
+
103
+ # Configuration matches updated assistant.py logic
104
+ instructions = "You are a test assistant. You HAVE access to uploaded files. Use the Retrieval tool to find the secret code."
105
+
106
+ configurable = {
107
+ "type": "agent",
108
+ "type==agent/agent_type": "GPT-4O",
109
+ "type==agent/llm_type": "gpt-4o",
110
+ "type==agent/model": "gpt-4o",
111
+ "type==agent/system_message": instructions,
112
+ "type==agent/tools": [
113
+ {
114
+ "id": f"retrieval-{uuid.uuid4().hex[:4]}",
115
+ "name": "Retrieval",
116
+ "type": "retrieval",
117
+ "description": "Look up information in uploaded files.",
118
+ "config": {}
119
+ },
120
+ {
121
+ "id": f"ddg-{uuid.uuid4().hex[:4]}",
122
+ "name": "DuckDuckGo Search",
123
+ "type": "ddg_search",
124
+ "description": "Search web",
125
+ "config": {}
126
+ }
127
+ ]
128
+ }
129
+
130
+ payload = {
131
+ "name": name,
132
+ "config": {
133
+ "configurable": configurable
134
+ },
135
+ "public": True
136
+ }
137
+
138
+ try:
139
+ print_info(f"Creating assistant: {name}")
140
+ resp = requests.post(f"{API_BASE_URL}/assistants/", headers=headers, json=payload)
141
+ if not resp.ok:
142
+ raise ValueError(f"Create assistant failed: {resp.text}")
143
+
144
+ data = resp.json()
145
+ self.assistant_id = data["assistant_id"]
146
+ print_pass(f"Created assistant {self.assistant_id}")
147
+
148
+ # List to verify
149
+ resp_list = requests.get(f"{API_BASE_URL}/assistants/", headers=headers)
150
+ if not resp_list.ok:
151
+ raise ValueError("List assistants failed")
152
+
153
+ assistants = resp_list.json()
154
+ found = any(a["assistant_id"] == self.assistant_id for a in assistants)
155
+ if found:
156
+ print_pass("Verification: Assistant found in list")
157
+ else:
158
+ print_fail("Verification: Assistant NOT found in list")
159
+
160
+ except Exception as e:
161
+ print_fail(f"Assistant step failed: {e}")
162
+ # Don't exit, might be able to cleanup other things
163
+
164
+ def step_4_files(self):
165
+ print_header("Step 4: File Management")
166
+ if not self.assistant_id:
167
+ print_fail("Skipping files (no assistant)")
168
+ return
169
+
170
+ # Create dummy file
171
+ with open(self.file_path, "w") as f:
172
+ f.write(f"CONFIDENTIAL DATA\nThe secret code is: {self.secret_code}")
173
+
174
+ headers = {"Authorization": f"Bearer {self.project_token}"}
175
+
176
+ try:
177
+ print_info(f"Uploading file with secret: {self.secret_code}")
178
+ with open(self.file_path, 'rb') as f:
179
+ files = {'files': (os.path.basename(self.file_path), f)}
180
+ resp = requests.post(f"{API_BASE_URL}/assistants/{self.assistant_id}/files", headers=headers, files=files)
181
+
182
+ if not resp.ok:
183
+ raise ValueError(f"Upload failed: {resp.text}")
184
+
185
+ print_pass("File uploaded successfully")
186
+
187
+ # List files
188
+ resp_list = requests.get(f"{API_BASE_URL}/assistants/{self.assistant_id}/files", headers=headers)
189
+ if resp_list.ok:
190
+ files_list = resp_list.json()
191
+ if len(files_list) > 0:
192
+ print_pass(f"Verified {len(files_list)} file(s) attached")
193
+ else:
194
+ print_fail("File list empty")
195
+
196
+ except Exception as e:
197
+ print_fail(f"File step failed: {e}")
198
+ finally:
199
+ if os.path.exists(self.file_path):
200
+ os.remove(self.file_path)
201
+
202
+ def step_5_threads(self):
203
+ print_header("Step 5: Thread Management")
204
+ if not self.project_token:
205
+ print_fail("Skipping threads (no project token)")
206
+ return
207
+
208
+ headers = {"Authorization": f"Bearer {self.project_token}"}
209
+ name = f"Test Thread {uuid.uuid4().hex[:4]}"
210
+
211
+ payload = {
212
+ "name": name,
213
+ "metadata": {"type": "thread"},
214
+ "assistant_id": self.assistant_id
215
+ }
216
+
217
+ try:
218
+ print_info(f"Creating thread: {name}")
219
+ resp = requests.post(f"{API_BASE_URL}/threads/", headers=headers, json=payload)
220
+ if not resp.ok:
221
+ raise ValueError(f"Create thread failed: {resp.text}")
222
+
223
+ data = resp.json()
224
+ self.thread_id = data["thread_id"]
225
+ print_pass(f"Created thread {self.thread_id}")
226
+
227
+ except Exception as e:
228
+ print_fail(f"Thread step failed: {e}")
229
+ sys.exit(1)
230
+
231
+ def step_6_execution(self):
232
+ print_header("Step 6: Execution (Runs & RAG)")
233
+ if not self.thread_id or not self.assistant_id:
234
+ print_fail("Skipping execution (missing resources)")
235
+ return
236
+
237
+ headers = {
238
+ "Authorization": f"Bearer {self.project_token}",
239
+ "Accept": "text/event-stream"
240
+ }
241
+
242
+ payload = {
243
+ "thread_id": self.thread_id,
244
+ "assistant_id": self.assistant_id,
245
+ "input": [{
246
+ "content": "What is the secret code in the file?",
247
+ "type": "human",
248
+ "role": "user"
249
+ }],
250
+ "stream_mode": ["messages", "values"]
251
+ }
252
+
253
+ print_info("Streaming run request...")
254
+ found_secret = False
255
+ full_response = ""
256
+
257
+ try:
258
+ resp = requests.post(f"{API_BASE_URL}/runs/stream", headers=headers, json=payload, stream=True)
259
+ if not resp.ok:
260
+ raise ValueError(f"Stream failed: {resp.status_code} {resp.text}")
261
+
262
+ for line in resp.iter_lines():
263
+ if line:
264
+ decoded = line.decode('utf-8')
265
+ if decoded.startswith("data:"):
266
+ data_str = decoded[5:].strip()
267
+ if data_str == "[DONE]":
268
+ break
269
+ try:
270
+ # Try to extract content delta or message
271
+ # This is a simplified check, actual structure varies
272
+ if self.secret_code in data_str:
273
+ found_secret = True
274
+ full_response += data_str
275
+ except:
276
+ pass
277
+
278
+ if found_secret:
279
+ print_pass(f"RAG Successful! Secret '{self.secret_code}' found in stream.")
280
+ else:
281
+ # Polling retry logic for latency
282
+ max_retries = 6
283
+ for i in range(max_retries):
284
+ wait_time = 10
285
+ print_info(f"Secret not found. Retrying in {wait_time}s ({i+1}/{max_retries})...")
286
+ time.sleep(wait_time)
287
+
288
+ full_response = ""
289
+ current_run_found = False
290
+
291
+ try:
292
+ resp = requests.post(f"{API_BASE_URL}/runs/stream", headers=headers, json=payload, stream=True)
293
+ for line in resp.iter_lines():
294
+ if line:
295
+ decoded = line.decode('utf-8')
296
+ if decoded.startswith("data:"):
297
+ data_str = decoded[5:].strip()
298
+ if data_str == "[DONE]":
299
+ break
300
+ try:
301
+ if self.secret_code in data_str:
302
+ current_run_found = True
303
+ full_response += data_str
304
+ except:
305
+ pass
306
+ except Exception as e:
307
+ print_fail(f"Retry {i+1} failed with error: {e}")
308
+ continue
309
+
310
+ if current_run_found:
311
+ found_secret = True
312
+ print_pass(f"RAG Successful (on retry {i+1})! Secret '{self.secret_code}' found.")
313
+ break
314
+
315
+ if not found_secret:
316
+ print_fail("Secret not found after retries.")
317
+ print_info(f"Last response dump (truncated): {full_response[:200]}...")
318
+
319
+ except Exception as e:
320
+ print_fail(f"Execution step failed: {e}")
321
+
322
+ def step_7_credits_check(self):
323
+ print_header("Step 7: Credits Purchase (Dry Run)")
324
+ # Just verify we can generate a checkout link
325
+ if not self.access_token:
326
+ return
327
+
328
+ headers = {
329
+ "Authorization": f"Bearer {self.access_token}",
330
+ "Content-Type": "application/json"
331
+ }
332
+ payload = {"quantity": 100, "total_amount": 10.0}
333
+
334
+ try:
335
+ resp = requests.post(f"{API_BASE_URL}/checkout/create-checkout-session", json=payload, headers=headers)
336
+ if resp.ok:
337
+ url = resp.json().get("url")
338
+ if url:
339
+ print_pass("Checkout session URL generated successfully")
340
+ else:
341
+ print_fail("No URL in checkout response")
342
+ else:
343
+ print_fail(f"Checkout creation failed: {resp.text}")
344
+ except Exception as e:
345
+ print_fail(f"Credits step failed: {e}")
346
+
347
+ def step_8_cleanup(self):
348
+ print_header("Step 8: Cleanup")
349
+ # cleanup is important
350
+
351
+ headers = {"Authorization": f"Bearer {self.access_token}"} # Use main token for project deletion context switching
352
+
353
+ if self.project_id:
354
+ # We need to delete the project.
355
+ # Note: Deleting project *should* delete assistants/threads/files inside it.
356
+ try:
357
+ # Switch context explicitly just in case or use main token? api.d.ts says delete project uses /projects/{id}
358
+ # And usually requires just access to it.
359
+
360
+ # Check api.d.ts: delete_project_projects__project_id__delete
361
+ # If confirm=true is needed
362
+ print_info(f"Deleting project {self.project_id}...")
363
+ resp = requests.delete(f"{API_BASE_URL}/projects/{self.project_id}?confirm=true", headers=headers)
364
+
365
+ if resp.ok:
366
+ print_pass("Project deleted successfully")
367
+ else:
368
+ print_fail(f"Project deletion failed: {resp.status_code} {resp.text}")
369
+
370
+ except Exception as e:
371
+ print_fail(f"Cleanup failed: {e}")
372
+
373
+ def run(self):
374
+ print_header("🚀 STARTING COMPREHENSIVE EPSIMO SKILL TEST 🚀")
375
+ self.step_1_auth()
376
+ self.step_2_projects()
377
+ self.step_3_assistants()
378
+ self.step_4_files()
379
+ self.step_5_threads()
380
+ self.step_6_execution()
381
+ self.step_7_credits_check()
382
+ self.step_8_cleanup()
383
+ print_header("🏁 TEST SUITE COMPLETE 🏁")
384
+
385
+ if __name__ == "__main__":
386
+ tester = SkillTester()
387
+ tester.run()
@@ -0,0 +1,83 @@
1
+
2
+ import sys
3
+ import os
4
+ import time
5
+ # Add the current directory to sys.path so we can import 'epsimo' and 'scripts'
6
+ sys.path.append(os.path.dirname(os.path.abspath(__file__)))
7
+
8
+ from epsimo import EpsimoClient
9
+ # Reuse auth helper to get a token for testing
10
+ from scripts.auth import get_token
11
+
12
+ def test_sdk():
13
+ print("🚀 Testing Epsimo Python SDK...")
14
+
15
+ # Authenticate
16
+ token = get_token()
17
+ client = EpsimoClient(api_key=token)
18
+ print("✅ Client initialized")
19
+
20
+ # 1. Create Project
21
+ p_name = f"SDK Test {int(time.time())}"
22
+ print(f"Creating project '{p_name}'...")
23
+ project = client.projects.create(name=p_name, description="SDK Test Project")
24
+ p_id = project["project_id"]
25
+ print(f"✅ Project Created: {p_id}")
26
+
27
+ # 2. Create Assistant
28
+ print("Creating assistant...")
29
+ asst = client.assistants.create(
30
+ project_id=p_id,
31
+ name="SDK Bot",
32
+ instructions="You are a bot created via the SDK.",
33
+ model="gpt-4o"
34
+ )
35
+ a_id = asst["assistant_id"]
36
+ print(f"✅ Assistant Created: {a_id}")
37
+
38
+ # 3. Create Thread
39
+ print("Creating thread...")
40
+ thread = client.threads.create(project_id=p_id, name="SDK Thread", assistant_id=a_id)
41
+ t_id = thread["thread_id"]
42
+ print(f"✅ Thread Created: {t_id}")
43
+
44
+ # 4. Stream Run
45
+ print("Streaming run...")
46
+ print("--- Output ---")
47
+ stream = client.threads.run_stream(
48
+ project_id=p_id,
49
+ thread_id=t_id,
50
+ assistant_id=a_id,
51
+ message="Say 'Hello from SDK'"
52
+ )
53
+
54
+ full_text = ""
55
+ for chunk in stream:
56
+ # print(f"DEBUG CHUNK: {chunk}")
57
+ content_found = None
58
+
59
+ if isinstance(chunk, list):
60
+ for item in chunk:
61
+ if isinstance(item, dict) and "content" in item:
62
+ content_found = item["content"]
63
+ break # Usually one message per chunk update in this mode
64
+ elif isinstance(chunk, dict) and "content" in chunk:
65
+ content_found = chunk["content"]
66
+
67
+ if content_found:
68
+ sys.stdout.write(content_found)
69
+ full_text += content_found
70
+
71
+ print("\n--- End Output ---")
72
+ if "Hello from SDK" in full_text:
73
+ print("✅ Streaming Success")
74
+ else:
75
+ print("⚠️ Streaming finished but expected text not found.")
76
+
77
+ # 5. Cleanup
78
+ print("Cleaning up...")
79
+ client.projects.delete(p_id, confirm=True)
80
+ print("✅ Project Deleted")
81
+
82
+ if __name__ == "__main__":
83
+ test_sdk()