melaya 0.1.2 → 0.2.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.
- checksums.yaml +4 -4
- data/LICENSE +158 -0
- data/README.md +244 -163
- data/lib/melaya/account.rb +30 -30
- data/lib/melaya/accounts.rb +42 -0
- data/lib/melaya/assistant.rb +41 -0
- data/lib/melaya/auth.rb +137 -0
- data/lib/melaya/backtest.rb +139 -101
- data/lib/melaya/billing.rb +75 -0
- data/lib/melaya/bugs.rb +67 -0
- data/lib/melaya/connectors.rb +71 -0
- data/lib/melaya/credentials.rb +255 -0
- data/lib/melaya/errors.rb +49 -15
- data/lib/melaya/evals.rb +78 -0
- data/lib/melaya/events.rb +425 -0
- data/lib/melaya/hitl.rb +114 -0
- data/lib/melaya/http_client.rb +226 -97
- data/lib/melaya/market.rb +198 -156
- data/lib/melaya/namespaces.rb +115 -0
- data/lib/melaya/phone.rb +75 -0
- data/lib/melaya/pipelines.rb +342 -0
- data/lib/melaya/projects.rb +58 -0
- data/lib/melaya/runner.rb +44 -0
- data/lib/melaya/sim.rb +110 -110
- data/lib/melaya/strategies.rb +170 -155
- data/lib/melaya/stream.rb +338 -336
- data/lib/melaya/team.rb +102 -0
- data/lib/melaya/templates.rb +148 -0
- data/lib/melaya/trade.rb +168 -168
- data/lib/melaya/version.rb +1 -1
- data/lib/melaya.rb +310 -79
- data/melaya.gemspec +28 -23
- metadata +32 -8
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Melaya
|
|
4
|
+
# Credentials API — store, retrieve, test, and delete secrets and
|
|
5
|
+
# third-party service connections at user scope.
|
|
6
|
+
#
|
|
7
|
+
# Credentials are envelope-encrypted at rest. Use +Melaya::ConnectorsAPI+
|
|
8
|
+
# for project-scoped connector credentials.
|
|
9
|
+
#
|
|
10
|
+
# Maps to /api/v1/private/credentials/*.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# melaya.credentials.set("openai", value: "sk-...")
|
|
14
|
+
# melaya.credentials.test("openai")
|
|
15
|
+
# melaya.credentials.delete("openai")
|
|
16
|
+
class CredentialsAPI
|
|
17
|
+
def initialize(http)
|
|
18
|
+
@http = http
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# GET /api/v1/private/credentials
|
|
22
|
+
# List all stored credentials (services, OAuth connections, env handles).
|
|
23
|
+
def list
|
|
24
|
+
@http.get("/api/v1/private/credentials")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# GET /api/v1/private/credentials/services
|
|
28
|
+
# List connected third-party services for the caller.
|
|
29
|
+
def connected_services
|
|
30
|
+
@http.get("/api/v1/private/credentials/services")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# GET /api/v1/private/credentials/:service
|
|
34
|
+
# Get a stored credential value by service name.
|
|
35
|
+
# @param service [String]
|
|
36
|
+
# @param key [String, nil] optional named key within the service
|
|
37
|
+
def get(service, key: nil)
|
|
38
|
+
params = key ? { "key" => key } : {}
|
|
39
|
+
@http.get("/api/v1/private/credentials/#{enc(service)}", params)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# PUT /api/v1/private/credentials/:service
|
|
43
|
+
# Store or update a credential (envelope-encrypted at rest).
|
|
44
|
+
# @param service [String]
|
|
45
|
+
# @param value [String] the secret value
|
|
46
|
+
# @param key [String, nil]
|
|
47
|
+
# @param label [String, nil]
|
|
48
|
+
def set(service, value:, key: nil, label: nil)
|
|
49
|
+
body = compact("value" => value, "key" => key, "label" => label)
|
|
50
|
+
@http.put("/api/v1/private/credentials/#{enc(service)}", body)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# DELETE /api/v1/private/credentials/:service
|
|
54
|
+
# Delete a stored credential by service name.
|
|
55
|
+
# @param service [String]
|
|
56
|
+
def delete(service)
|
|
57
|
+
@http.delete("/api/v1/private/credentials/#{enc(service)}")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# POST /api/v1/private/credentials/:service/test
|
|
61
|
+
# Test a stored credential (e.g. validate API key against its service).
|
|
62
|
+
# @param service [String]
|
|
63
|
+
def test(service)
|
|
64
|
+
@http.post("/api/v1/private/credentials/#{enc(service)}/test")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# ── Operator profile ───────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
# GET /api/v1/private/credentials/operator-profile
|
|
70
|
+
# Get operator profile (persona config for agent context).
|
|
71
|
+
def get_operator_profile
|
|
72
|
+
@http.get("/api/v1/private/credentials/operator-profile")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# PUT /api/v1/private/credentials/operator-profile
|
|
76
|
+
# Save operator profile.
|
|
77
|
+
# @param profile [Hash]
|
|
78
|
+
def set_operator_profile(profile)
|
|
79
|
+
@http.put("/api/v1/private/credentials/operator-profile", profile)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# ── AI models ──────────────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
# GET /api/v1/private/credentials/models
|
|
85
|
+
# List available AI models across all configured providers.
|
|
86
|
+
# Collapses 19+ provider fan-out into a parameterized query.
|
|
87
|
+
# @param provider [String, nil]
|
|
88
|
+
# @param capability [String, nil]
|
|
89
|
+
def list_models(provider: nil, capability: nil)
|
|
90
|
+
@http.get("/api/v1/private/credentials/models",
|
|
91
|
+
compact("provider" => provider, "capability" => capability))
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# ── Melaya sub-accounts ────────────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
# GET /api/v1/private/credentials/melaya-accounts
|
|
97
|
+
# List Melaya sub-accounts available to the caller.
|
|
98
|
+
def melaya_accounts
|
|
99
|
+
@http.get("/api/v1/private/credentials/melaya-accounts")
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# ── RAG ────────────────────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
# POST /api/v1/private/rag/ingest
|
|
105
|
+
# Start a RAG document ingestion job.
|
|
106
|
+
# @param body [Hash]
|
|
107
|
+
def rag_ingest_start(body = {})
|
|
108
|
+
@http.post("/api/v1/private/rag/ingest", body)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# GET /api/v1/private/rag/ingest/:sessionId
|
|
112
|
+
# Poll RAG ingestion job status.
|
|
113
|
+
# @param session_id [String]
|
|
114
|
+
def rag_ingest_status(session_id)
|
|
115
|
+
@http.get("/api/v1/private/rag/ingest/#{enc(session_id)}")
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# POST /api/v1/private/rag/retrieve
|
|
119
|
+
# Start a RAG retrieval query.
|
|
120
|
+
# @param body [Hash]
|
|
121
|
+
def rag_retrieve_start(body = {})
|
|
122
|
+
@http.post("/api/v1/private/rag/retrieve", body)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# GET /api/v1/private/rag/retrieve/:sessionId
|
|
126
|
+
# Poll RAG retrieval result.
|
|
127
|
+
# @param session_id [String]
|
|
128
|
+
def rag_retrieve_status(session_id)
|
|
129
|
+
@http.get("/api/v1/private/rag/retrieve/#{enc(session_id)}")
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# ── Folder picker ──────────────────────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
# POST /api/v1/private/rag/pick-folder
|
|
135
|
+
# Initiate native folder picker for file ingestion.
|
|
136
|
+
# @param body [Hash]
|
|
137
|
+
def pick_folder_start(body = {})
|
|
138
|
+
@http.post("/api/v1/private/rag/pick-folder", body)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# GET /api/v1/private/rag/pick-folder/:sessionId
|
|
142
|
+
# Poll folder picker result.
|
|
143
|
+
# @param session_id [String]
|
|
144
|
+
def pick_folder_status(session_id)
|
|
145
|
+
@http.get("/api/v1/private/rag/pick-folder/#{enc(session_id)}")
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# ── LinkedIn OAuth ─────────────────────────────────────────────────────────
|
|
149
|
+
|
|
150
|
+
# POST /api/v1/private/credentials/linkedin/connect
|
|
151
|
+
# Start LinkedIn OAuth flow.
|
|
152
|
+
def linkedin_connect_start(body = {})
|
|
153
|
+
@http.post("/api/v1/private/credentials/linkedin/connect", body)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# DELETE /api/v1/private/credentials/linkedin/connect
|
|
157
|
+
# Cancel an in-progress LinkedIn OAuth flow.
|
|
158
|
+
def linkedin_connect_cancel
|
|
159
|
+
@http.delete("/api/v1/private/credentials/linkedin/connect")
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# GET /api/v1/private/credentials/linkedin/connect/status
|
|
163
|
+
# Poll LinkedIn OAuth connection status.
|
|
164
|
+
def linkedin_connect_status
|
|
165
|
+
@http.get("/api/v1/private/credentials/linkedin/connect/status")
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# ── Luma OAuth ────────────────────────────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
# POST /api/v1/private/credentials/luma/connect
|
|
171
|
+
# Start Luma OAuth flow.
|
|
172
|
+
def luma_connect_start(body = {})
|
|
173
|
+
@http.post("/api/v1/private/credentials/luma/connect", body)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# GET /api/v1/private/credentials/luma/connect/status
|
|
177
|
+
# Poll Luma OAuth connection status.
|
|
178
|
+
def luma_connect_status
|
|
179
|
+
@http.get("/api/v1/private/credentials/luma/connect/status")
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# DELETE /api/v1/private/credentials/luma/connect
|
|
183
|
+
# Cancel an in-progress Luma OAuth flow.
|
|
184
|
+
def luma_connect_cancel
|
|
185
|
+
@http.delete("/api/v1/private/credentials/luma/connect")
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# GET /api/v1/private/credentials/luma/schema
|
|
189
|
+
# Get the Luma event registration form schema.
|
|
190
|
+
# @param params [Hash]
|
|
191
|
+
def luma_schema(params = {})
|
|
192
|
+
@http.get("/api/v1/private/credentials/luma/schema", params)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# ── Google OAuth ───────────────────────────────────────────────────────────
|
|
196
|
+
|
|
197
|
+
# POST /api/v1/private/credentials/google/oauth
|
|
198
|
+
# Start Google OAuth flow for credential storage.
|
|
199
|
+
def google_oauth_start(body = {})
|
|
200
|
+
@http.post("/api/v1/private/credentials/google/oauth", body)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# ── CLI auth ───────────────────────────────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
# POST /api/v1/private/credentials/cli-auth
|
|
206
|
+
# Start CLI authentication flow (device-code style).
|
|
207
|
+
def cli_auth_start(body = {})
|
|
208
|
+
@http.post("/api/v1/private/credentials/cli-auth", body)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# ── NotebookLM ─────────────────────────────────────────────────────────────
|
|
212
|
+
|
|
213
|
+
# POST /api/v1/private/credentials/notebooklm/login
|
|
214
|
+
# Store NotebookLM credentials.
|
|
215
|
+
def notebooklm_login(body = {})
|
|
216
|
+
@http.post("/api/v1/private/credentials/notebooklm/login", body)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# GET /api/v1/private/credentials/notebooklm/status
|
|
220
|
+
# Check NotebookLM connection status.
|
|
221
|
+
def notebooklm_status
|
|
222
|
+
@http.get("/api/v1/private/credentials/notebooklm/status")
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# ── Telegram auth ──────────────────────────────────────────────────────────
|
|
226
|
+
|
|
227
|
+
# POST /api/v1/private/credentials/telegram/auth
|
|
228
|
+
# Start Telegram user auth (phone number step).
|
|
229
|
+
def telegram_auth_start(body = {})
|
|
230
|
+
@http.post("/api/v1/private/credentials/telegram/auth", body)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# POST /api/v1/private/credentials/telegram/auth/code
|
|
234
|
+
# Submit Telegram SMS verification code.
|
|
235
|
+
def telegram_auth_code(body = {})
|
|
236
|
+
@http.post("/api/v1/private/credentials/telegram/auth/code", body)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# POST /api/v1/private/credentials/telegram/auth/2fa
|
|
240
|
+
# Submit Telegram 2FA password.
|
|
241
|
+
def telegram_auth_2fa(body = {})
|
|
242
|
+
@http.post("/api/v1/private/credentials/telegram/auth/2fa", body)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
private
|
|
246
|
+
|
|
247
|
+
def enc(s)
|
|
248
|
+
URI.encode_www_form_component(s.to_s)
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def compact(hash)
|
|
252
|
+
hash.reject { |_, v| v.nil? }
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
data/lib/melaya/errors.rb
CHANGED
|
@@ -1,15 +1,49 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Melaya
|
|
4
|
-
# Raised for non-2xx REST responses or ok:false envelopes.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Melaya
|
|
4
|
+
# Raised for non-2xx REST responses or ok:false response envelopes.
|
|
5
|
+
#
|
|
6
|
+
# Two server error shapes are normalised here:
|
|
7
|
+
# 1. { error: 'tier_insufficient', tier: '...' } → HTTP 403
|
|
8
|
+
# 2. { error: '...', message: '...', code: '...' } → other 4xx/5xx
|
|
9
|
+
class MelayaError < StandardError
|
|
10
|
+
# HTTP status code (Integer), or 0 for transport/WS errors.
|
|
11
|
+
attr_reader :status
|
|
12
|
+
# Short machine-readable error code from the server (String or nil).
|
|
13
|
+
attr_reader :code
|
|
14
|
+
# Raw response body (Hash, String, or nil). Not included in #message so
|
|
15
|
+
# secrets embedded in error payloads do not leak to log lines.
|
|
16
|
+
attr_reader :body
|
|
17
|
+
|
|
18
|
+
def initialize(message, status: 0, code: nil, body: nil)
|
|
19
|
+
super(message)
|
|
20
|
+
@status = status
|
|
21
|
+
@code = code
|
|
22
|
+
@body = body
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Raised when the server returns { error: 'tier_insufficient' } (HTTP 403).
|
|
27
|
+
# Check +#tier+ for the minimum required tier string.
|
|
28
|
+
class TierInsufficientError < MelayaError
|
|
29
|
+
attr_reader :tier
|
|
30
|
+
|
|
31
|
+
def initialize(tier: nil, body: nil)
|
|
32
|
+
@tier = tier
|
|
33
|
+
super("Melaya: feature requires a higher tier (#{tier})", status: 403, code: "tier_insufficient", body: body)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Raised when the server returns HTTP 429 (rate-limited) after all retries
|
|
38
|
+
# are exhausted.
|
|
39
|
+
class RateLimitError < MelayaError
|
|
40
|
+
# Retry-After header value in seconds, if present.
|
|
41
|
+
attr_reader :retry_after
|
|
42
|
+
|
|
43
|
+
def initialize(retry_after: nil, body: nil)
|
|
44
|
+
@retry_after = retry_after
|
|
45
|
+
super("Melaya: rate limit exceeded#{retry_after ? " (retry after #{retry_after}s)" : ""}",
|
|
46
|
+
status: 429, code: "rate_limited", body: body)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
data/lib/melaya/evals.rb
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Melaya
|
|
4
|
+
# Evals API — list and inspect agent evaluation run results.
|
|
5
|
+
#
|
|
6
|
+
# Maps to /api/v1/private/evals/*.
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# runs = melaya.evals.list_runs
|
|
10
|
+
# summary = melaya.evals.summary
|
|
11
|
+
# detail = melaya.evals.run_detail(runs.first["id"])
|
|
12
|
+
class EvalsAPI
|
|
13
|
+
def initialize(http)
|
|
14
|
+
@http = http
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# GET /api/v1/private/evals/runs
|
|
18
|
+
# List eval run results for the caller's tenant.
|
|
19
|
+
def list_runs(params = {})
|
|
20
|
+
@http.get("/api/v1/private/evals/runs", params)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# GET /api/v1/private/evals/summary
|
|
24
|
+
# Get aggregate summary of eval results.
|
|
25
|
+
def summary
|
|
26
|
+
@http.get("/api/v1/private/evals/summary")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# GET /api/v1/private/evals/runs/:runId
|
|
30
|
+
# Get detailed results for a specific eval run.
|
|
31
|
+
# @param run_id [String]
|
|
32
|
+
def run_detail(run_id)
|
|
33
|
+
@http.get("/api/v1/private/evals/runs/#{enc(run_id)}")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# GET /api/v1/private/evals/compare
|
|
37
|
+
# Compare results across multiple eval runs.
|
|
38
|
+
# @param params [Hash] e.g. { "runIds" => "id1,id2" }
|
|
39
|
+
def compare(params = {})
|
|
40
|
+
@http.get("/api/v1/private/evals/compare", params)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# GET /api/v1/private/evals/memory-graph
|
|
44
|
+
# Get memory graph visualization data for eval runs.
|
|
45
|
+
def memory_graph(params = {})
|
|
46
|
+
@http.get("/api/v1/private/evals/memory-graph", params)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# GET /api/v1/private/evals/runs/:runId/memory
|
|
50
|
+
# Get memory usage for a specific eval run.
|
|
51
|
+
# @param run_id [String]
|
|
52
|
+
def run_memory(run_id)
|
|
53
|
+
@http.get("/api/v1/private/evals/runs/#{enc(run_id)}/memory")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# GET /api/v1/private/evals/crew-memory
|
|
57
|
+
# Get agent crew memory for a pipeline.
|
|
58
|
+
# @param pipeline [String]
|
|
59
|
+
# @param project [String]
|
|
60
|
+
def crew_memory(pipeline:, project:)
|
|
61
|
+
@http.get("/api/v1/private/evals/crew-memory",
|
|
62
|
+
"pipeline" => pipeline,
|
|
63
|
+
"project" => project)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# GET /api/v1/private/evals/benchmarks
|
|
67
|
+
# Get benchmark scores across eval runs.
|
|
68
|
+
def benchmarks(params = {})
|
|
69
|
+
@http.get("/api/v1/private/evals/benchmarks", params)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def enc(s)
|
|
75
|
+
URI.encode_www_form_component(s.to_s)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|