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,57 @@
1
+ # Virtual Database via Epsimo Threads
2
+
3
+ This guide explains how to use Epsimo threads as a persistent, structured storage layer for your application using the **Virtual Database** pattern.
4
+
5
+ ## The Concept
6
+
7
+ Instead of managing a separate database, your assistant can maintain its own structured state directly within an Epsimo thread. This data is persistent, indexed by thread, and can be queried both by the assistant and your frontend.
8
+
9
+ ## 1. Defining the Storage Tool (Skill)
10
+
11
+ To allow an assistant to write to its database, provide it with a tool that handles data recording.
12
+
13
+ ### Tool Definition (JSON)
14
+ ```json
15
+ {
16
+ "name": "update_database",
17
+ "description": "Persist structured data to the thread state.",
18
+ "parameters": {
19
+ "type": "object",
20
+ "properties": {
21
+ "key": { "type": "string", "description": "The field name (e.g., 'user_preferences')" },
22
+ "value": { "type": "object", "description": "The data to store (JSON object)" }
23
+ },
24
+ "required": ["key", "value"]
25
+ }
26
+ }
27
+ ```
28
+
29
+ ## 2. Reading Data via SDK
30
+
31
+ Use the newly implemented `c.db` resource in the `epsimo` Python SDK to retrieve the structured state.
32
+
33
+ ```python
34
+ from epsimo import EpsimoClient
35
+
36
+ client = EpsimoClient(api_key="your-token")
37
+
38
+ # Get everything
39
+ db = client.db.get_all(project_id, thread_id)
40
+
41
+ # Get specific key
42
+ preferences = client.db.get(project_id, thread_id, "user_preferences")
43
+ print(f"Theme: {preferences.get('theme')}")
44
+ ```
45
+
46
+ ## 3. Writing Data via CLI (Admin/Testing)
47
+
48
+ You can also manually seed coordinates into the "database" using the CLI:
49
+
50
+ ```bash
51
+ epsimo db set --project-id PROJ_ID --thread-id THREAD_ID --key "status" --value '"active"'
52
+ ```
53
+
54
+ ## Benefits
55
+ - **Zero Configuration**: No database server required.
56
+ - **Contextual Storage**: Data is naturally partitioned by conversation.
57
+ - **Agent Awareness**: The assistant always "knows" what's in its DB because it is part of the thread state.
@@ -0,0 +1,2 @@
1
+ requests>=2.0.0
2
+ PyYAML>=6.0.0
@@ -0,0 +1,165 @@
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 create_assistant(project_id, name, instructions, model, tools=None):
10
+ token = get_token()
11
+
12
+ # Get project specific token
13
+ project_token = get_project_token(project_id)
14
+
15
+ headers = {"Authorization": f"Bearer {project_token}"}
16
+
17
+ # Construct tools list similar to frontend
18
+ final_tools = []
19
+ if tools:
20
+ for t_name in tools:
21
+ t_lower = t_name.lower()
22
+ if t_lower == "retrieval" or t_lower == "retriever":
23
+ final_tools.append({
24
+ "id": f"retrieval-{os.urandom(4).hex()}",
25
+ "name": "Retrieval",
26
+ "type": "retrieval",
27
+ "description": "Look up information in uploaded files.",
28
+ "config": {}
29
+ })
30
+ elif "duckduckgo" in t_lower or "ddg" in t_lower:
31
+ final_tools.append({
32
+ "id": f"ddg_search-{os.urandom(4).hex()}",
33
+ "name": "DuckDuckGo Search",
34
+ "type": "ddg_search",
35
+ "description": "Uses the DuckDuckGo search engine to find information on the web.",
36
+ "config": {}
37
+ })
38
+ elif "tavily" in t_lower:
39
+ final_tools.append({
40
+ "id": f"search_tavily-{os.urandom(4).hex()}",
41
+ "name": "Search (Tavily)",
42
+ "type": "search_tavily",
43
+ "description": "Uses the Tavily search engine. Includes sources in the response.",
44
+ "config": {}
45
+ })
46
+ else:
47
+ # Generic fallback
48
+ final_tools.append({
49
+ "id": f"{t_name}-{os.urandom(4).hex()}",
50
+ "name": t_name,
51
+ "type": t_name,
52
+ "description": "",
53
+ "config": {}
54
+ })
55
+
56
+ # New config structure based on AssistantModal.tsx
57
+ configurable = {
58
+ "type": "agent",
59
+ "type==agent/agent_type": model.upper(),
60
+ "type==agent/llm_type": model,
61
+ "type==agent/model": model,
62
+ "type==agent/system_message": instructions,
63
+ "type==agent/tools": final_tools
64
+ }
65
+
66
+ payload = {
67
+ "name": name,
68
+ "config": {
69
+ "configurable": configurable
70
+ },
71
+ "public": True
72
+ }
73
+
74
+ response = requests.post(f"{API_BASE_URL}/assistants/", headers=headers, json=payload)
75
+ if not response.ok:
76
+ print(f"Error: {response.text}")
77
+ response.raise_for_status()
78
+ print(json.dumps(response.json(), indent=2))
79
+
80
+ def list_assistants(project_id):
81
+ token = get_token()
82
+
83
+ # Get project specific token
84
+ auth_headers = {"Authorization": f"Bearer {token}"}
85
+ proj_resp = requests.get(f"{API_BASE_URL}/projects/{project_id}", headers=auth_headers)
86
+ proj_resp.raise_for_status()
87
+ data = proj_resp.json()
88
+ project_token = data.get('access_token') or data.get('token') or data.get('jwt_token')
89
+
90
+ headers = {"Authorization": f"Bearer {project_token}"}
91
+ response = requests.get(f"{API_BASE_URL}/assistants/", headers=headers)
92
+ response.raise_for_status()
93
+ print(json.dumps(response.json(), indent=2))
94
+
95
+ def update_assistant(project_id, assistant_id, name=None, instructions=None, model=None, tools=None):
96
+ token = get_token()
97
+
98
+ # Get project specific token
99
+ auth_headers = {"Authorization": f"Bearer {token}"}
100
+ proj_resp = requests.get(f"{API_BASE_URL}/projects/{project_id}", headers=auth_headers)
101
+ proj_resp.raise_for_status()
102
+ data = proj_resp.json()
103
+ project_token = data.get('access_token') or data.get('token') or data.get('jwt_token')
104
+
105
+ headers = {"Authorization": f"Bearer {project_token}"}
106
+
107
+ # Fetch existing assistant data first to merge
108
+ existing_resp = requests.get(f"{API_BASE_URL}/assistants/{assistant_id}", headers=headers)
109
+ existing_resp.raise_for_status()
110
+ existing_data = existing_resp.json()
111
+
112
+ config = existing_data.get("config", {})
113
+ if instructions:
114
+ config["system_prompt"] = instructions
115
+ if model:
116
+ config["model"] = model
117
+ if tools is not None:
118
+ config["tools"] = tools
119
+
120
+ payload = {
121
+ "name": name if name else existing_data.get("name"),
122
+ "config": config,
123
+ "public": existing_data.get("public", True)
124
+ }
125
+
126
+ response = requests.put(f"{API_BASE_URL}/assistants/{assistant_id}", headers=headers, json=payload)
127
+ response.raise_for_status()
128
+ print(json.dumps(response.json(), indent=2))
129
+
130
+ def main():
131
+ parser = argparse.ArgumentParser(description="Manage EpsimoAI Assistants")
132
+ subparsers = parser.add_subparsers(dest="command", required=True)
133
+
134
+ # List
135
+ list_parser = subparsers.add_parser("list", help="List assistants in a project")
136
+ list_parser.add_argument("--project-id", required=True, help="Project ID")
137
+
138
+ # Create
139
+ create_parser = subparsers.add_parser("create", help="Create a new assistant")
140
+ create_parser.add_argument("--project-id", required=True, help="Project ID")
141
+ create_parser.add_argument("--name", required=True, help="Assistant name")
142
+ create_parser.add_argument("--instructions", required=True, help="System instructions")
143
+ create_parser.add_argument("--model", default="gpt-4o", help="Model name (default: gpt-4o)")
144
+ create_parser.add_argument("--tools", nargs="*", help="List of tools to enable (e.g. Retrieval DuckDuckGo)")
145
+
146
+ # Update
147
+ update_parser = subparsers.add_parser("update", help="Update an existing assistant")
148
+ update_parser.add_argument("--project-id", required=True, help="Project ID")
149
+ update_parser.add_argument("--assistant-id", required=True, help="Assistant ID")
150
+ update_parser.add_argument("--name", help="New assistant name")
151
+ update_parser.add_argument("--instructions", help="New system instructions")
152
+ update_parser.add_argument("--model", help="New model name")
153
+ update_parser.add_argument("--tools", nargs="*", help="New list of tools")
154
+
155
+ args = parser.parse_args()
156
+
157
+ if args.command == "list":
158
+ list_assistants(args.project_id)
159
+ elif args.command == "create":
160
+ create_assistant(args.project_id, args.name, args.instructions, args.model, tools=args.tools)
161
+ elif args.command == "update":
162
+ update_assistant(args.project_id, args.assistant_id, args.name, args.instructions, args.model, tools=args.tools)
163
+
164
+ if __name__ == "__main__":
165
+ main()
@@ -0,0 +1,195 @@
1
+
2
+ import os
3
+ import json
4
+ import time
5
+ import requests
6
+ import argparse
7
+ import getpass
8
+ import sys
9
+ import subprocess
10
+ from pathlib import Path
11
+
12
+ # Configuration
13
+ API_BASE_URL = os.environ.get("EPSIMO_API_URL", "https://api.epsimoagents.com")
14
+ TOKEN_FILE = Path("/Users/thierry/code/epsimo-frontend/.epsimo_token")
15
+
16
+ def get_token():
17
+ """Retrieve a valid JWT token, refreshing if necessary."""
18
+
19
+ # Check if token file exists
20
+ if TOKEN_FILE.exists():
21
+ try:
22
+ with open(TOKEN_FILE, 'r') as f:
23
+ data = json.load(f)
24
+ return data.get('access_token') or data.get('token') or data.get('jwt_token')
25
+ except json.JSONDecodeError:
26
+ pass
27
+
28
+ # Try logging in with env vars if file doesn't exist/is invalid
29
+ if os.environ.get("EPSIMO_EMAIL") and os.environ.get("EPSIMO_PASSWORD"):
30
+ return perform_login(os.environ.get("EPSIMO_EMAIL"), os.environ.get("EPSIMO_PASSWORD"))
31
+
32
+ raise RuntimeError("Authentication required. Please run `python3 .agent/skills/epsimo-agent/scripts/auth.py login` or set EPSIMO_EMAIL and EPSIMO_PASSWORD environment variables.")
33
+
34
+ def get_project_token(project_id):
35
+ """Get a project-specific token."""
36
+ token = get_token()
37
+ headers = {"Authorization": f"Bearer {token}"}
38
+ response = requests.get(f"{API_BASE_URL}/projects/{project_id}", headers=headers)
39
+ response.raise_for_status()
40
+ data = response.json()
41
+ project_token = data.get('access_token') or data.get('token') or data.get('jwt_token')
42
+ if not project_token:
43
+ raise ValueError(f"Failed to obtain project token for project {project_id}")
44
+ return project_token
45
+
46
+ def perform_signup(email, password):
47
+ """Register a new user."""
48
+ print(f"Attempting to register new user: {email}")
49
+ url = f"{API_BASE_URL}/auth/signup"
50
+ payload = {
51
+ "email": email,
52
+ "password": password
53
+ }
54
+
55
+ try:
56
+ response = requests.post(url, json=payload)
57
+ response.raise_for_status()
58
+ print("✅ Signup successful! Logging in...")
59
+ return perform_login(email, password)
60
+ except requests.exceptions.HTTPError as e:
61
+ if e.response.status_code == 400: # Usually "User already exists" or validation error
62
+ print(f"⚠️ Signup failed: {e.response.text}")
63
+ else:
64
+ print(f"❌ Signup failed: {e}")
65
+ raise
66
+
67
+ def get_input_via_applescript(prompt, hidden=False):
68
+ """Get input using macOS native dialog."""
69
+ try:
70
+ script = f'display dialog "{prompt}" default answer ""'
71
+ if hidden:
72
+ script += ' with hidden answer'
73
+ script += ' buttons {"Cancel", "OK"} default button "OK"'
74
+
75
+ cmd = ['osascript', '-e', script]
76
+ result = subprocess.run(cmd, capture_output=True, text=True, check=True)
77
+ # Parse result: button returned:OK, text returned:foobar
78
+ output = result.stdout.strip()
79
+ if "text returned:" in output:
80
+ return output.split("text returned:")[-1]
81
+ return ""
82
+ except subprocess.CalledProcessError:
83
+ return None
84
+
85
+ def perform_login(email, password, attempt_signup_on_fail=False):
86
+ """Login logic."""
87
+ if not email or not password:
88
+ raise ValueError("Email and password are required.")
89
+
90
+ url = f"{API_BASE_URL}/auth/login"
91
+ payload = {
92
+ "email": email,
93
+ "password": password
94
+ }
95
+
96
+ try:
97
+ response = requests.post(url, json=payload)
98
+ response.raise_for_status()
99
+
100
+ data = response.json()
101
+
102
+ # DEBUG: Print keys if structure is unexpected
103
+ token = data.get('access_token')
104
+
105
+ if not token:
106
+ token = data.get('token')
107
+
108
+ if not token:
109
+ token = data.get('jwt_token')
110
+
111
+ if not token:
112
+ print(f"⚠️ Debug: Full response data keys: {list(data.keys())}")
113
+ print(f"⚠️ Debug: Full response data: {data}")
114
+ raise ValueError("Failed to obtain access token from login response.")
115
+
116
+ # Save token
117
+ with open(TOKEN_FILE, 'w') as f:
118
+ json.dump(data, f)
119
+
120
+ print(f"✅ Successfully logged in as {email}")
121
+ return token
122
+
123
+ except requests.exceptions.HTTPError as e:
124
+ print(f"❌ Login failed: {e}")
125
+ # If UNAUTHORIZED, it might mean user doesn't exist OR bad password.
126
+ if attempt_signup_on_fail:
127
+ print("\nUser might not exist or password is wrong.")
128
+
129
+ # Check if we should use GUI for prompt
130
+ if sys.platform == 'darwin' and os.environ.get("USE_GUI_PROMPT"):
131
+ # Simple popup confirming desire to signup? Hard to do strictly y/n
132
+ # Just use CLI for choices for simplicity, or assume yes if they explicitly ran setup
133
+ pass
134
+
135
+ choice = input("Do you want to create a new account with these credentials? (y/n): ")
136
+ if choice.lower() == 'y':
137
+ return perform_signup(email, password)
138
+ raise
139
+
140
+ if __name__ == "__main__":
141
+ parser = argparse.ArgumentParser(description="EpsimoAI Authentication")
142
+ subparsers = parser.add_subparsers(dest="command", help="Commands")
143
+
144
+ # Login command
145
+ login_parser = subparsers.add_parser("login", help="Authenticate with EpsimoAI")
146
+ login_parser.add_argument("--email", help="Your email address")
147
+ login_parser.add_argument("--password", help="Your password (optional, will prompt if missing)")
148
+ login_parser.add_argument("--modal", action="store_true", help="Use macOS modal dialogs for input")
149
+
150
+ args = parser.parse_args()
151
+
152
+ if args.command == "login":
153
+ email = args.email
154
+ password = args.password
155
+ use_modal = args.modal or os.environ.get("USE_GUI_PROMPT") == "1"
156
+
157
+ if not email:
158
+ if use_modal and sys.platform == 'darwin':
159
+ email = get_input_via_applescript("Enter your Epsimo Email:")
160
+ else:
161
+ email = input("Email: ")
162
+
163
+ if not email:
164
+ print("Email is required.")
165
+ sys.exit(1)
166
+
167
+ if not password:
168
+ if 'EPSIMO_PASSWORD' in os.environ:
169
+ password = os.environ['EPSIMO_PASSWORD']
170
+ elif use_modal and sys.platform == 'darwin':
171
+ password = get_input_via_applescript("Enter your Epsimo Password:", hidden=True)
172
+ else:
173
+ password = getpass.getpass("Password: ")
174
+
175
+ if not password:
176
+ print("Password is required.")
177
+ sys.exit(1)
178
+
179
+ try:
180
+ # We enable auto-signup prompt for interactive login
181
+ # Pass choice to use GUI prompts if needed
182
+ if use_modal:
183
+ os.environ["USE_GUI_PROMPT"] = "1"
184
+ perform_login(email, password, attempt_signup_on_fail=True)
185
+ except Exception:
186
+ sys.exit(1)
187
+
188
+ else:
189
+ # Default behavior: test token retrieval
190
+ try:
191
+ token = get_token()
192
+ print(f"Token: {token[:10]}...")
193
+ except Exception as e:
194
+ print(f"Error: {e}")
195
+ sys.exit(1)
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env python3
2
+ import sys
3
+ import os
4
+ import argparse
5
+ import json
6
+ import requests
7
+ from auth import get_token, login
8
+
9
+ BASE_URL = os.environ.get("EPSIMO_API_URL", "https://api.epsimoagents.com")
10
+
11
+ def check_balance():
12
+ """Check the current thread and credit balance."""
13
+ token = get_token()
14
+ if not token:
15
+ print("Not authenticated. Please login first.")
16
+ return
17
+
18
+ try:
19
+ # Try retrieving full thread info
20
+ headers = {"Authorization": f"Bearer {token}"}
21
+ response = requests.get(f"{BASE_URL}/auth/thread-info", headers=headers)
22
+
23
+ if response.status_code == 200:
24
+ data = response.json()
25
+ thread_count = data.get("thread_counter", 0)
26
+ thread_max = data.get("thread_max", 0)
27
+ remaining = thread_max - thread_count
28
+
29
+ print("\n=== Thread Balance ===")
30
+ print(f"Threads Used: {thread_count}")
31
+ print(f"Total Allowance: {thread_max}")
32
+ print(f"Threads Remaining: {remaining}")
33
+ print("======================\n")
34
+ else:
35
+ print(f"Error checking balance: {response.text}")
36
+
37
+ except Exception as e:
38
+ print(f"Error: {e}")
39
+
40
+ def buy_credits(quantity, total_amount=None):
41
+ """Create a checkout session to buy credits."""
42
+ token = get_token()
43
+ if not token:
44
+ print("Not authenticated. Please login first.")
45
+ return
46
+
47
+ # Simple logic for price estimation if not provided
48
+ # Assuming roughly €0.10 per thread for small amounts, cheaper for bulk
49
+ if total_amount is None:
50
+ if quantity >= 1000:
51
+ price_per_unit = 0.08
52
+ elif quantity >= 500:
53
+ price_per_unit = 0.09
54
+ else:
55
+ price_per_unit = 0.10
56
+
57
+ total_amount = round(quantity * price_per_unit, 2)
58
+ print(f"Estimated cost: {total_amount} EUR")
59
+
60
+ try:
61
+ headers = {
62
+ "Authorization": f"Bearer {token}",
63
+ "Content-Type": "application/json"
64
+ }
65
+
66
+ payload = {
67
+ "quantity": quantity,
68
+ "total_amount": float(total_amount)
69
+ }
70
+
71
+ print("Creating checkout session...")
72
+ response = requests.post(f"{BASE_URL}/checkout/create-checkout-session", json=payload, headers=headers)
73
+
74
+ if response.status_code == 200:
75
+ data = response.json()
76
+ checkout_url = data.get("url")
77
+ print("\nCheckout session created successfully!")
78
+ print(f"Please visit this URL to complete your purchase:\n\n{checkout_url}\n")
79
+ else:
80
+ print(f"Error creating checkout session: {response.text}")
81
+
82
+ except Exception as e:
83
+ print(f"Error: {e}")
84
+
85
+ def main():
86
+ parser = argparse.ArgumentParser(description="Manage credits and thread usage")
87
+ subparsers = parser.add_subparsers(dest="command", help="Command to run")
88
+
89
+ # Balance command
90
+ subparsers.add_parser("balance", help="Check current credit balance")
91
+
92
+ # Buy command
93
+ buy_parser = subparsers.add_parser("buy", help="Buy more credits")
94
+ buy_parser.add_argument("--quantity", type=int, required=True, help="Number of threads to purchase")
95
+ buy_parser.add_argument("--amount", type=float, help="Total amount to pay in EUR (optional, calculated if omitted)")
96
+
97
+ args = parser.parse_args()
98
+
99
+ if args.command == "balance":
100
+ check_balance()
101
+ elif args.command == "buy":
102
+ buy_credits(args.quantity, args.amount)
103
+ else:
104
+ parser.print_help()
105
+
106
+ if __name__ == "__main__":
107
+ main()
@@ -0,0 +1,41 @@
1
+
2
+ import requests
3
+ import os
4
+ import json
5
+ from auth import get_token, API_BASE_URL
6
+
7
+ projectId = "5d4025a5-5db7-4b6c-99b0-5b65e97d88c0"
8
+ assistantId = "a4178aa5-63d3-468b-8672-c6d916153d0a"
9
+ threadId = "2b4c6325-c400-4e78-81c8-ef7781bf8b49"
10
+ message = "Research Google"
11
+
12
+ def debug_run():
13
+ token = get_token()
14
+ auth_headers = {"Authorization": f"Bearer {token}"}
15
+ proj_resp = requests.get(f"{API_BASE_URL}/projects/{projectId}", headers=auth_headers)
16
+ data = proj_resp.json()
17
+ project_token = data.get('access_token')
18
+
19
+ headers = {
20
+ "Authorization": f"Bearer {project_token}",
21
+ "Accept": "text/event-stream"
22
+ }
23
+
24
+ payload = {
25
+ "assistant_id": assistantId,
26
+ "thread_id": threadId,
27
+ "input": [{"type": "human", "content": message}]
28
+ }
29
+
30
+ print(f"Testing url: {API_BASE_URL}/runs/stream")
31
+ r1 = requests.post(f"{API_BASE_URL}/runs/stream", headers=headers, json=payload, allow_redirects=False)
32
+ print(f"No Slash: {r1.status_code}")
33
+ print(r1.headers)
34
+
35
+ print(f"Testing url: {API_BASE_URL}/runs/stream/")
36
+ r2 = requests.post(f"{API_BASE_URL}/runs/stream/", headers=headers, json=payload, allow_redirects=False)
37
+ print(f"With Slash: {r2.status_code}")
38
+ print(r2.headers)
39
+
40
+ if __name__ == "__main__":
41
+ debug_run()
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Example helper script for epsimo-agent
4
+
5
+ This is a placeholder script that can be executed directly.
6
+ Replace with actual implementation or delete if not needed.
7
+
8
+ Example real scripts from other skills:
9
+ - pdf/scripts/fill_fillable_fields.py - Fills PDF form fields
10
+ - pdf/scripts/convert_pdf_to_images.py - Converts PDF pages to images
11
+ """
12
+
13
+ def main():
14
+ print("This is an example script for epsimo-agent")
15
+ # TODO: Add actual script logic here
16
+ # This could be data processing, file conversion, API calls, etc.
17
+
18
+ if __name__ == "__main__":
19
+ main()
@@ -0,0 +1,73 @@
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 list_files(project_id, assistant_id):
10
+ token = get_token()
11
+
12
+ # Get project specific token
13
+ project_token = get_project_token(project_id)
14
+
15
+ headers = {"Authorization": f"Bearer {project_token}"}
16
+ response = requests.get(f"{API_BASE_URL}/assistants/{assistant_id}/files", headers=headers)
17
+ response.raise_for_status()
18
+ print(json.dumps(response.json(), indent=2))
19
+
20
+ def upload_file(project_id, assistant_id, file_path):
21
+ token = get_token()
22
+
23
+ # Get project specific token
24
+ auth_headers = {"Authorization": f"Bearer {token}"}
25
+ proj_resp = requests.get(f"{API_BASE_URL}/projects/{project_id}", headers=auth_headers)
26
+ proj_resp.raise_for_status()
27
+ data = proj_resp.json()
28
+ project_token = data.get('access_token') or data.get('token') or data.get('jwt_token')
29
+
30
+ headers = {"Authorization": f"Bearer {project_token}"}
31
+
32
+ if not os.path.exists(file_path):
33
+ print(f"Error: File not found at {file_path}")
34
+ return
35
+
36
+ with open(file_path, 'rb') as f:
37
+ files = {'files': (os.path.basename(file_path), f)}
38
+ response = requests.post(
39
+ f"{API_BASE_URL}/assistants/{assistant_id}/files",
40
+ headers=headers,
41
+ files=files
42
+ )
43
+
44
+ if not response.ok:
45
+ print(f"Error: {response.text}")
46
+
47
+ response.raise_for_status()
48
+ print(json.dumps(response.json(), indent=2))
49
+
50
+ def main():
51
+ parser = argparse.ArgumentParser(description="Manage EpsimoAI Assistant Files")
52
+ subparsers = parser.add_subparsers(dest="command", required=True)
53
+
54
+ # List
55
+ list_parser = subparsers.add_parser("list", help="List files for an assistant")
56
+ list_parser.add_argument("--project-id", required=True, help="Project ID")
57
+ list_parser.add_argument("--assistant-id", required=True, help="Assistant ID")
58
+
59
+ # Upload
60
+ upload_parser = subparsers.add_parser("upload", help="Upload a file to an assistant")
61
+ upload_parser.add_argument("--project-id", required=True, help="Project ID")
62
+ upload_parser.add_argument("--assistant-id", required=True, help="Assistant ID")
63
+ upload_parser.add_argument("--file", required=True, help="Path to file to upload")
64
+
65
+ args = parser.parse_args()
66
+
67
+ if args.command == "list":
68
+ list_files(args.project_id, args.assistant_id)
69
+ elif args.command == "upload":
70
+ upload_file(args.project_id, args.assistant_id, args.file)
71
+
72
+ if __name__ == "__main__":
73
+ main()