@ralioco/sdk 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.
@@ -0,0 +1,317 @@
1
+ import { webcrypto } from 'node:crypto';
2
+
3
+ /**
4
+ * ES256 / DPoP crypto primitives for the Ralio machine-auth path.
5
+ *
6
+ * Everything here mirrors what the Ralio API expects byte-for-byte:
7
+ *
8
+ * - P-256 (ES256) is the only curve the token endpoint accepts.
9
+ * - The public JWK is the canonical RFC 7638 form (`crv`/`kty`/`x`/`y` only),
10
+ * so the thumbprint computed here matches the `cnf.jkt` the server stamps on
11
+ * the access token and the fingerprint the owner confirms.
12
+ * - Client assertions follow RFC 7521/7523; DPoP proofs follow RFC 9449.
13
+ */
14
+
15
+ /**
16
+ * Web-Crypto `CryptoKey`, re-exported from `node:crypto` so consumers don't
17
+ * need DOM lib types. This is what `jose` v6 returns from `importPKCS8` and
18
+ * `generateKeyPair` and what we accept everywhere a private key is needed.
19
+ */
20
+ type CryptoKey = webcrypto.CryptoKey;
21
+ /** Canonical RFC 7638 public JWK — exactly these members, in this order. */
22
+ interface PublicJwk {
23
+ crv: "P-256";
24
+ kty: "EC";
25
+ x: string;
26
+ y: string;
27
+ }
28
+
29
+ /**
30
+ * Access-token lifecycle for the machine path.
31
+ *
32
+ * A {@link TokenManager} mints tokens via the `private_key_jwt` client
33
+ * assertion, caches the access token until shortly before expiry, and rotates
34
+ * the refresh token. All token mutations are serialised behind a lock:
35
+ *
36
+ * > Presenting a previously-rotated refresh token is treated as a replay
37
+ * > attack and revokes the whole chain.
38
+ *
39
+ * so two concurrent callers must never race a refresh.
40
+ */
41
+
42
+ interface TokenManagerOptions {
43
+ clientId: string;
44
+ privateKey: CryptoKey;
45
+ kid: string;
46
+ tokenUrl: string;
47
+ scopes?: string[];
48
+ /** Refresh this many seconds before the token actually expires. */
49
+ refreshLeewaySeconds?: number;
50
+ }
51
+ declare class TokenManager {
52
+ private readonly clientId;
53
+ private readonly privateKey;
54
+ private readonly kid;
55
+ private readonly tokenUrl;
56
+ private readonly scopes?;
57
+ private readonly leewayMs;
58
+ private accessTokenValue;
59
+ private refreshTokenValue;
60
+ private expiresAtMs;
61
+ private chain;
62
+ constructor(opts: TokenManagerOptions);
63
+ /** Return a valid access token, minting or refreshing as needed. */
64
+ accessToken(): Promise<string>;
65
+ /** Discard the cached token and obtain a fresh one. Used after a 401. */
66
+ forceRefresh(): Promise<string>;
67
+ private withLock;
68
+ private obtain;
69
+ private mint;
70
+ private refresh;
71
+ private exchange;
72
+ }
73
+
74
+ /**
75
+ * Response types returned by the SDK.
76
+ *
77
+ * Parsing is forgiving: unknown fields the API may add later are ignored, so a
78
+ * server-side addition never breaks a pinned client.
79
+ */
80
+ type Json = Record<string, unknown>;
81
+ /**
82
+ * The result of a completed registration. `clientId` is the `cb_…` handle used
83
+ * to mint tokens; the private key lives on disk.
84
+ */
85
+ interface CredentialBinding {
86
+ clientId: string;
87
+ scopes: string[];
88
+ }
89
+ interface Message {
90
+ id: string;
91
+ role: string;
92
+ content: string;
93
+ createdAt: string | null;
94
+ }
95
+ /** A synchronous `chat.send` response. */
96
+ interface ChatReply {
97
+ reply: string;
98
+ conversationId: string;
99
+ newMessages: Message[];
100
+ }
101
+ /**
102
+ * One server-sent event from `chat.stream`.
103
+ *
104
+ * `event` is the SSE event name (`conversation`, `tool_started`,
105
+ * `tool_completed`, `tool_failed`, `text_delta`, `reply`, `error`); `data` is
106
+ * the decoded JSON payload. `text` is a convenience for the common
107
+ * `text_delta` / `reply` case (empty string when absent).
108
+ */
109
+ interface ChatStreamEvent {
110
+ event: string;
111
+ data: Json;
112
+ text: string;
113
+ }
114
+ interface Transaction {
115
+ id: string;
116
+ amount: string;
117
+ currency: string;
118
+ status: string;
119
+ date: string | null;
120
+ creditor: string | null;
121
+ debtor: string | null;
122
+ reference: string | null;
123
+ paymentIntentId: string | null;
124
+ }
125
+
126
+ /**
127
+ * DPoP-bound HTTP transport.
128
+ *
129
+ * Every request carries `Authorization: DPoP <token>` plus a freshly-signed
130
+ * `DPoP` proof for that exact method + URL + token. On a 401 the transport
131
+ * refreshes the token once and retries with a brand-new proof (the proof `jti`
132
+ * is single-use server-side, so the retry must re-sign).
133
+ */
134
+
135
+ interface TransportOptions {
136
+ baseUrl: string;
137
+ tokens: TokenManager;
138
+ privateKey: CryptoKey;
139
+ publicJwk: PublicJwk;
140
+ /** Per-request timeout in ms. Applies to `request`, not to SSE streams. */
141
+ requestTimeoutMs?: number;
142
+ }
143
+ interface RequestOptions {
144
+ jsonBody?: Record<string, unknown>;
145
+ params?: Record<string, string | number | undefined>;
146
+ }
147
+ declare class Transport {
148
+ private readonly baseUrl;
149
+ private readonly tokens;
150
+ private readonly privateKey;
151
+ private readonly publicJwk;
152
+ private readonly requestTimeoutMs?;
153
+ constructor(opts: TransportOptions);
154
+ request(method: string, path: string, opts?: RequestOptions): Promise<Response>;
155
+ streamSse(method: string, path: string, opts?: {
156
+ jsonBody?: Record<string, unknown>;
157
+ }): AsyncGenerator<ChatStreamEvent>;
158
+ private buildUrl;
159
+ private authHeaders;
160
+ }
161
+
162
+ /** Chat resource — drive an agent with natural language (`agents:execute`). */
163
+
164
+ interface ChatParams {
165
+ agentId: string;
166
+ message: string;
167
+ conversationId?: string;
168
+ }
169
+ declare class ChatResource {
170
+ private readonly transport;
171
+ constructor(transport: Transport);
172
+ /**
173
+ * Send a message and wait for the agent's complete reply.
174
+ *
175
+ * Times out server-side after 120s. For interactive approval flows where a
176
+ * human may take longer, use {@link stream} instead.
177
+ */
178
+ send(params: ChatParams): Promise<ChatReply>;
179
+ /**
180
+ * Stream the agent's reply as server-sent events.
181
+ *
182
+ * Yields `conversation`, `tool_started`/`tool_completed`/`tool_failed`,
183
+ * `text_delta`, and a final `reply` event.
184
+ */
185
+ stream(params: ChatParams): AsyncGenerator<ChatStreamEvent>;
186
+ }
187
+
188
+ /** Transactions resource — read executed payments (`transactions:read`). */
189
+
190
+ interface ListTransactionsParams {
191
+ agentId?: string;
192
+ limit?: number;
193
+ }
194
+ declare class TransactionsResource {
195
+ private readonly transport;
196
+ constructor(transport: Transport);
197
+ /** List transactions across the caller's agents, newest first. */
198
+ list(params?: ListTransactionsParams): Promise<Transaction[]>;
199
+ }
200
+
201
+ /** The top-level {@link RalioClient}. */
202
+
203
+ interface RalioClientOptions {
204
+ clientId: string;
205
+ /** Path to the PKCS8 PEM private key written by {@link register}. */
206
+ privateKeyPath: string;
207
+ baseUrl?: string;
208
+ scopes?: string[];
209
+ /** Per-request timeout in ms (default 30000). Streams are not bounded. */
210
+ timeoutMs?: number;
211
+ }
212
+ /**
213
+ * Client for the Ralio API, authenticated via a credential binding (OAuth 2.1
214
+ * `client_credentials` + `private_key_jwt` + DPoP).
215
+ *
216
+ * Obtain `clientId` and the private key once via {@link register}, then:
217
+ *
218
+ * ```ts
219
+ * const client = await RalioClient.create({
220
+ * clientId: "cb_...",
221
+ * privateKeyPath: "ralio-key.pem",
222
+ * });
223
+ * const reply = await client.chat.send({ agentId: "...", message: "What's my balance?" });
224
+ * ```
225
+ */
226
+ declare class RalioClient {
227
+ readonly chat: ChatResource;
228
+ readonly transactions: TransactionsResource;
229
+ private constructor();
230
+ /** Load the private key and build a ready-to-use client. */
231
+ static create(opts: RalioClientOptions): Promise<RalioClient>;
232
+ /**
233
+ * Release resources. Present for API symmetry and forward compatibility;
234
+ * the native `fetch` transport holds no long-lived connections of its own.
235
+ */
236
+ close(): void;
237
+ [Symbol.dispose](): void;
238
+ }
239
+
240
+ /**
241
+ * One-time credential-binding registration (the operator side).
242
+ *
243
+ * The owner mints a `ralio-reg-…` ticket in the console. The operator calls
244
+ * {@link register} on the agent host: it generates a P-256 keypair locally,
245
+ * submits the public key with the ticket, and polls until the owner approves
246
+ * the binding in the console. The private key never leaves the host.
247
+ */
248
+
249
+ declare const DEFAULT_BASE_URL = "https://api.ralio.co";
250
+ interface RegisterOptions {
251
+ ticket: string;
252
+ privateKeyPath: string;
253
+ baseUrl?: string;
254
+ requestedScopes?: string[];
255
+ clientMetadata?: Record<string, unknown>;
256
+ /** Poll interval in milliseconds (default 3000). */
257
+ pollIntervalMs?: number;
258
+ /** Overall timeout in milliseconds (default 900000 = 15 min). */
259
+ timeoutMs?: number;
260
+ /** Replace an existing key file. Off by default to avoid clobbering. */
261
+ overwrite?: boolean;
262
+ }
263
+ /**
264
+ * Run the registration flow and return the approved binding.
265
+ *
266
+ * Generates a keypair, writes the private key to `privateKeyPath`, and blocks
267
+ * until the owner approves (up to `timeoutMs`). Rejects with a
268
+ * {@link RalioRegistrationError} if the binding is rejected, expires, or the
269
+ * timeout elapses.
270
+ */
271
+ declare function register(opts: RegisterOptions): Promise<CredentialBinding>;
272
+
273
+ /** Exception hierarchy for the Ralio SDK. */
274
+ /** Base class for every error raised by the SDK. */
275
+ declare class RalioError extends Error {
276
+ constructor(message: string);
277
+ }
278
+ /** Local configuration problem — missing key, bad arguments. */
279
+ declare class RalioConfigError extends RalioError {
280
+ }
281
+ /** A credential-binding registration was rejected, expired, or timed out. */
282
+ declare class RalioRegistrationError extends RalioError {
283
+ }
284
+ /**
285
+ * An error response from the Ralio API.
286
+ *
287
+ * Carries the HTTP `statusCode`, the server-supplied `detail` string, and the
288
+ * `WWW-Authenticate` challenge when present (DPoP/OAuth failures put the
289
+ * specific reason there).
290
+ */
291
+ declare class RalioAPIError extends RalioError {
292
+ readonly statusCode: number;
293
+ readonly detail: string | null;
294
+ readonly wwwAuthenticate: string | null;
295
+ constructor(message: string, opts: {
296
+ statusCode: number;
297
+ detail?: string | null;
298
+ wwwAuthenticate?: string | null;
299
+ });
300
+ }
301
+ /** 401 — missing/invalid token, failed client assertion, or rejected proof. */
302
+ declare class RalioAuthError extends RalioAPIError {
303
+ }
304
+ /** 403 — token lacks the required scope, or the resource isn't owned. */
305
+ declare class RalioPermissionError extends RalioAPIError {
306
+ }
307
+ /** 404 — resource does not exist. */
308
+ declare class RalioNotFoundError extends RalioAPIError {
309
+ }
310
+ /** 422 — invalid field values or a business-rule violation. */
311
+ declare class RalioValidationError extends RalioAPIError {
312
+ }
313
+ /** 429 — rate limited. Back off and retry. */
314
+ declare class RalioRateLimitError extends RalioAPIError {
315
+ }
316
+
317
+ export { type ChatParams, type ChatReply, type ChatStreamEvent, type CredentialBinding, DEFAULT_BASE_URL, type ListTransactionsParams, type Message, RalioAPIError, RalioAuthError, RalioClient, type RalioClientOptions, RalioConfigError, RalioError, RalioNotFoundError, RalioPermissionError, RalioRateLimitError, RalioRegistrationError, RalioValidationError, type RegisterOptions, type Transaction, register };