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.
- package/LICENSE +21 -0
- package/SKILL.md +85 -0
- package/assets/example_asset.txt +24 -0
- package/epsimo/__init__.py +3 -0
- package/epsimo/__main__.py +4 -0
- package/epsimo/auth.py +143 -0
- package/epsimo/cli.py +586 -0
- package/epsimo/client.py +53 -0
- package/epsimo/resources/assistants.py +47 -0
- package/epsimo/resources/credits.py +16 -0
- package/epsimo/resources/db.py +31 -0
- package/epsimo/resources/files.py +39 -0
- package/epsimo/resources/projects.py +30 -0
- package/epsimo/resources/threads.py +83 -0
- package/epsimo/templates/components/AuthModal/AuthModal.module.css +39 -0
- package/epsimo/templates/components/AuthModal/AuthModal.tsx +138 -0
- package/epsimo/templates/components/BuyCredits/BuyCreditsModal.module.css +96 -0
- package/epsimo/templates/components/BuyCredits/BuyCreditsModal.tsx +132 -0
- package/epsimo/templates/components/BuyCredits/CreditsDisplay.tsx +101 -0
- package/epsimo/templates/components/ThreadChat/ThreadChat.module.css +551 -0
- package/epsimo/templates/components/ThreadChat/ThreadChat.tsx +862 -0
- package/epsimo/templates/components/ThreadChat/components/ToolRenderers.module.css +509 -0
- package/epsimo/templates/components/ThreadChat/components/ToolRenderers.tsx +322 -0
- package/epsimo/templates/next-mvp/app/globals.css.tmpl +20 -0
- package/epsimo/templates/next-mvp/app/layout.tsx.tmpl +22 -0
- package/epsimo/templates/next-mvp/app/page.module.css.tmpl +84 -0
- package/epsimo/templates/next-mvp/app/page.tsx.tmpl +43 -0
- package/epsimo/templates/next-mvp/epsimo.yaml.tmpl +12 -0
- package/epsimo/templates/next-mvp/package.json.tmpl +26 -0
- package/epsimo/tools/library.yaml +51 -0
- package/package.json +27 -0
- package/references/api_reference.md +34 -0
- package/references/virtual_db_guide.md +57 -0
- package/requirements.txt +2 -0
- package/scripts/assistant.py +165 -0
- package/scripts/auth.py +195 -0
- package/scripts/credits.py +107 -0
- package/scripts/debug_run.py +41 -0
- package/scripts/example.py +19 -0
- package/scripts/files.py +73 -0
- package/scripts/find_thread.py +55 -0
- package/scripts/project.py +60 -0
- package/scripts/run.py +75 -0
- package/scripts/test_all_skills.py +387 -0
- package/scripts/test_sdk.py +83 -0
- package/scripts/test_streaming.py +167 -0
- package/scripts/test_vdb.py +65 -0
- package/scripts/thread.py +77 -0
- 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.
|
package/requirements.txt
ADDED
|
@@ -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()
|
package/scripts/auth.py
ADDED
|
@@ -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()
|
package/scripts/files.py
ADDED
|
@@ -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()
|