melaya 0.1.4 → 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 +96 -15
- 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 +38 -0
- 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 +36 -2
- 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 +159 -30
- data/lib/melaya/market.rb +42 -0
- 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/strategies.rb +15 -0
- data/lib/melaya/stream.rb +5 -3
- data/lib/melaya/team.rb +102 -0
- data/lib/melaya/templates.rb +148 -0
- data/lib/melaya/version.rb +1 -1
- data/lib/melaya.rb +261 -30
- data/melaya.gemspec +10 -5
- metadata +28 -7
data/lib/melaya/bugs.rb
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Melaya
|
|
4
|
+
# Bugs API — submit and track bug reports.
|
|
5
|
+
#
|
|
6
|
+
# Maps to /api/v1/private/bugs/*.
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# melaya.bugs.create(title: "UI crash", description: "...")
|
|
10
|
+
# reports = melaya.bugs.list_mine
|
|
11
|
+
class BugsAPI
|
|
12
|
+
def initialize(http)
|
|
13
|
+
@http = http
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# POST /api/v1/private/bugs
|
|
17
|
+
# Submit a bug report (user-facing feedback form).
|
|
18
|
+
# @param title [String]
|
|
19
|
+
# @param description [String]
|
|
20
|
+
# @param extra [Hash] any additional fields
|
|
21
|
+
def create(title:, description: nil, **extra)
|
|
22
|
+
body = extra.transform_keys(&:to_s)
|
|
23
|
+
body["title"] = title
|
|
24
|
+
body["description"] = description unless description.nil?
|
|
25
|
+
@http.post("/api/v1/private/bugs", body)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# GET /api/v1/private/bugs/mine
|
|
29
|
+
# List bug reports submitted by the caller.
|
|
30
|
+
def list_mine
|
|
31
|
+
@http.get("/api/v1/private/bugs/mine")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# GET /api/v1/private/bugs/:bugId
|
|
35
|
+
# Get a single bug report by ID.
|
|
36
|
+
# @param bug_id [String]
|
|
37
|
+
def get(bug_id)
|
|
38
|
+
@http.get("/api/v1/private/bugs/#{enc(bug_id)}")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# POST /api/v1/private/bugs/:bugId/comments
|
|
42
|
+
# Add a comment to a bug report.
|
|
43
|
+
# @param bug_id [String]
|
|
44
|
+
# @param comment [String]
|
|
45
|
+
def add_comment(bug_id, comment:)
|
|
46
|
+
@http.post("/api/v1/private/bugs/#{enc(bug_id)}/comments", "comment" => comment)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# GET /api/v1/private/bugs/notifications
|
|
50
|
+
# List unread bug-related notifications for the caller.
|
|
51
|
+
def list_notifications
|
|
52
|
+
@http.get("/api/v1/private/bugs/notifications")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# POST /api/v1/private/bugs/notifications/read
|
|
56
|
+
# Mark bug notifications as read.
|
|
57
|
+
def mark_notifications_read(body = {})
|
|
58
|
+
@http.post("/api/v1/private/bugs/notifications/read", body)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def enc(s)
|
|
64
|
+
URI.encode_www_form_component(s.to_s)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Melaya
|
|
4
|
+
# Project Connectors API — manage credentials at project scope.
|
|
5
|
+
#
|
|
6
|
+
# Project-scoped connectors are isolated per project, letting separate
|
|
7
|
+
# projects use different API keys for the same service.
|
|
8
|
+
#
|
|
9
|
+
# Maps to /api/v1/private/projects/:project/connectors/*.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# melaya.connectors.set("my-project", "openai", value: "sk-...")
|
|
13
|
+
# services = melaya.connectors.connected_services("my-project")
|
|
14
|
+
class ConnectorsAPI
|
|
15
|
+
def initialize(http)
|
|
16
|
+
@http = http
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# GET /api/v1/private/projects/:project/connectors/services
|
|
20
|
+
# List connected services for a project.
|
|
21
|
+
# @param project [String] project name
|
|
22
|
+
def connected_services(project)
|
|
23
|
+
@http.get("/api/v1/private/projects/#{enc(project)}/connectors/services")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# PUT /api/v1/private/projects/:project/connectors/:service
|
|
27
|
+
# Store a connector credential at project scope.
|
|
28
|
+
# @param project [String]
|
|
29
|
+
# @param service [String]
|
|
30
|
+
# @param value [String]
|
|
31
|
+
# @param key [String, nil]
|
|
32
|
+
# @param label [String, nil]
|
|
33
|
+
def set(project, service, value:, key: nil, label: nil)
|
|
34
|
+
body = compact("value" => value, "key" => key, "label" => label)
|
|
35
|
+
@http.put("/api/v1/private/projects/#{enc(project)}/connectors/#{enc(service)}", body)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# DELETE /api/v1/private/projects/:project/connectors/:service
|
|
39
|
+
# Delete a project-scoped connector credential.
|
|
40
|
+
# @param project [String]
|
|
41
|
+
# @param service [String]
|
|
42
|
+
def delete(project, service)
|
|
43
|
+
@http.delete("/api/v1/private/projects/#{enc(project)}/connectors/#{enc(service)}")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# POST /api/v1/private/projects/:project/connectors/env-handle
|
|
47
|
+
# Get a short-lived env-handle token for project-scoped credentials.
|
|
48
|
+
# @param project [String]
|
|
49
|
+
def env_handle(project)
|
|
50
|
+
@http.post("/api/v1/private/projects/#{enc(project)}/connectors/env-handle")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# POST /api/v1/private/projects/:project/connectors/google/oauth
|
|
54
|
+
# Start Google OAuth flow for project-scoped connector.
|
|
55
|
+
# @param project [String]
|
|
56
|
+
# @param body [Hash]
|
|
57
|
+
def google_oauth_start(project, body = {})
|
|
58
|
+
@http.post("/api/v1/private/projects/#{enc(project)}/connectors/google/oauth", body)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def enc(s)
|
|
64
|
+
URI.encode_www_form_component(s.to_s)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def compact(hash)
|
|
68
|
+
hash.reject { |_, v| v.nil? }
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -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,9 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Melaya
|
|
4
|
-
# Raised for non-2xx REST responses or ok:false envelopes.
|
|
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
|
|
5
9
|
class MelayaError < StandardError
|
|
6
|
-
|
|
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
|
|
7
17
|
|
|
8
18
|
def initialize(message, status: 0, code: nil, body: nil)
|
|
9
19
|
super(message)
|
|
@@ -12,4 +22,28 @@ module Melaya
|
|
|
12
22
|
@body = body
|
|
13
23
|
end
|
|
14
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
|
|
15
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
|