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.
@@ -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
- class MelayaError < StandardError
6
- attr_reader :status, :code, :body
7
-
8
- def initialize(message, status: 0, code: nil, body: nil)
9
- super(message)
10
- @status = status
11
- @code = code
12
- @body = body
13
- end
14
- end
15
- end
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
@@ -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