@synova-cloud/sdk 1.0.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/dist/index.cjs ADDED
@@ -0,0 +1,630 @@
1
+ 'use strict';
2
+
3
+ // src/errors/index.ts
4
+ var SynovaError = class extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = "SynovaError";
8
+ Object.setPrototypeOf(this, new.target.prototype);
9
+ }
10
+ };
11
+ var ApiSynovaError = class extends SynovaError {
12
+ code;
13
+ httpCode;
14
+ requestId;
15
+ timestamp;
16
+ constructor(response) {
17
+ super(response.message);
18
+ this.name = "ApiSynovaError";
19
+ this.code = response.code;
20
+ this.httpCode = response.httpCode;
21
+ this.requestId = response.requestId;
22
+ this.timestamp = response.timestamp;
23
+ }
24
+ };
25
+ var AuthSynovaError = class extends SynovaError {
26
+ constructor(message = "Invalid or missing API key") {
27
+ super(message);
28
+ this.name = "AuthSynovaError";
29
+ }
30
+ };
31
+ var NotFoundSynovaError = class extends SynovaError {
32
+ resourceType;
33
+ resourceId;
34
+ constructor(resourceType, resourceId) {
35
+ super(`${resourceType} not found: ${resourceId}`);
36
+ this.name = "NotFoundSynovaError";
37
+ this.resourceType = resourceType;
38
+ this.resourceId = resourceId;
39
+ }
40
+ };
41
+ var RateLimitSynovaError = class extends SynovaError {
42
+ retryAfterMs;
43
+ constructor(message = "Rate limit exceeded", retryAfterMs) {
44
+ super(message);
45
+ this.name = "RateLimitSynovaError";
46
+ this.retryAfterMs = retryAfterMs;
47
+ }
48
+ };
49
+ var TimeoutSynovaError = class extends SynovaError {
50
+ timeoutMs;
51
+ constructor(timeoutMs) {
52
+ super(`Request timed out after ${timeoutMs}ms`);
53
+ this.name = "TimeoutSynovaError";
54
+ this.timeoutMs = timeoutMs;
55
+ }
56
+ };
57
+ var NetworkSynovaError = class extends SynovaError {
58
+ cause;
59
+ constructor(message, cause) {
60
+ super(message);
61
+ this.name = "NetworkSynovaError";
62
+ this.cause = cause;
63
+ }
64
+ };
65
+ var ServerSynovaError = class extends SynovaError {
66
+ httpCode;
67
+ retryable;
68
+ constructor(httpCode, message) {
69
+ super(message || `Server error: ${httpCode}`);
70
+ this.name = "ServerSynovaError";
71
+ this.httpCode = httpCode;
72
+ this.retryable = true;
73
+ }
74
+ };
75
+
76
+ // src/utils/http.ts
77
+ var HttpClient = class {
78
+ config;
79
+ constructor(config) {
80
+ this.config = config;
81
+ }
82
+ async request(options) {
83
+ const url = this.buildUrl(options.path, options.query);
84
+ const { maxRetries } = this.config.retry;
85
+ let lastError = null;
86
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
87
+ try {
88
+ if (attempt > 0) {
89
+ const delay = this.calculateRetryDelay(attempt, lastError);
90
+ this.log(
91
+ `Retry attempt ${attempt}/${maxRetries} after ${delay}ms (strategy: ${this.config.retry.strategy})`
92
+ );
93
+ await this.sleep(delay);
94
+ }
95
+ return await this.makeRequest(url, options);
96
+ } catch (error) {
97
+ lastError = error;
98
+ if (!this.isRetryable(error)) {
99
+ throw error;
100
+ }
101
+ if (attempt === maxRetries) {
102
+ throw error;
103
+ }
104
+ this.log(`Request failed, will retry: ${error.message}`);
105
+ }
106
+ }
107
+ throw lastError;
108
+ }
109
+ async upload(options) {
110
+ const url = this.buildUrl(options.path);
111
+ const { maxRetries } = this.config.retry;
112
+ let lastError = null;
113
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
114
+ try {
115
+ if (attempt > 0) {
116
+ const delay = this.calculateRetryDelay(attempt, lastError);
117
+ this.log(
118
+ `Retry attempt ${attempt}/${maxRetries} after ${delay}ms (strategy: ${this.config.retry.strategy})`
119
+ );
120
+ await this.sleep(delay);
121
+ }
122
+ return await this.makeUploadRequest(url, options.formData);
123
+ } catch (error) {
124
+ lastError = error;
125
+ if (!this.isRetryable(error)) {
126
+ throw error;
127
+ }
128
+ if (attempt === maxRetries) {
129
+ throw error;
130
+ }
131
+ this.log(`Upload failed, will retry: ${error.message}`);
132
+ }
133
+ }
134
+ throw lastError;
135
+ }
136
+ async makeRequest(url, options) {
137
+ const controller = new AbortController();
138
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
139
+ this.log(`${options.method} ${url}`);
140
+ try {
141
+ const response = await fetch(url, {
142
+ method: options.method,
143
+ headers: {
144
+ Authorization: `Bearer ${this.config.apiKey}`,
145
+ "Content-Type": "application/json",
146
+ "User-Agent": "synova-cloud-sdk-node/0.1.0"
147
+ },
148
+ body: options.body ? JSON.stringify(options.body) : void 0,
149
+ signal: controller.signal
150
+ });
151
+ clearTimeout(timeoutId);
152
+ if (!response.ok) {
153
+ await this.handleErrorResponse(response);
154
+ }
155
+ const data = await response.json();
156
+ this.log(`Response: ${response.status}`);
157
+ return data;
158
+ } catch (error) {
159
+ clearTimeout(timeoutId);
160
+ if (error instanceof Error && error.name === "AbortError") {
161
+ throw new TimeoutSynovaError(this.config.timeout);
162
+ }
163
+ if (error instanceof ApiSynovaError || error instanceof AuthSynovaError || error instanceof NotFoundSynovaError || error instanceof RateLimitSynovaError || error instanceof ServerSynovaError) {
164
+ throw error;
165
+ }
166
+ throw new NetworkSynovaError(
167
+ `Network request failed: ${error.message}`,
168
+ error
169
+ );
170
+ }
171
+ }
172
+ async makeUploadRequest(url, formData) {
173
+ const controller = new AbortController();
174
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
175
+ this.log(`POST (upload) ${url}`);
176
+ try {
177
+ const response = await fetch(url, {
178
+ method: "POST",
179
+ headers: {
180
+ Authorization: `Bearer ${this.config.apiKey}`,
181
+ "User-Agent": "synova-cloud-sdk-node/0.1.0"
182
+ // Note: Content-Type is not set for FormData - browser/node sets it with boundary
183
+ },
184
+ body: formData,
185
+ signal: controller.signal
186
+ });
187
+ clearTimeout(timeoutId);
188
+ if (!response.ok) {
189
+ await this.handleErrorResponse(response);
190
+ }
191
+ const data = await response.json();
192
+ this.log(`Response: ${response.status}`);
193
+ return data;
194
+ } catch (error) {
195
+ clearTimeout(timeoutId);
196
+ if (error instanceof Error && error.name === "AbortError") {
197
+ throw new TimeoutSynovaError(this.config.timeout);
198
+ }
199
+ if (error instanceof ApiSynovaError || error instanceof AuthSynovaError || error instanceof NotFoundSynovaError || error instanceof RateLimitSynovaError || error instanceof ServerSynovaError) {
200
+ throw error;
201
+ }
202
+ throw new NetworkSynovaError(
203
+ `Upload request failed: ${error.message}`,
204
+ error
205
+ );
206
+ }
207
+ }
208
+ async handleErrorResponse(response) {
209
+ const status = response.status;
210
+ let errorBody = null;
211
+ try {
212
+ errorBody = await response.json();
213
+ } catch {
214
+ }
215
+ if (status === 401) {
216
+ throw new AuthSynovaError(errorBody?.message);
217
+ }
218
+ if (status === 404) {
219
+ throw new NotFoundSynovaError("Resource", errorBody?.message || "unknown");
220
+ }
221
+ if (status === 429) {
222
+ const retryAfter = response.headers.get("Retry-After");
223
+ const retryAfterMs = retryAfter ? parseInt(retryAfter, 10) * 1e3 : void 0;
224
+ throw new RateLimitSynovaError(errorBody?.message, retryAfterMs);
225
+ }
226
+ if (status >= 500) {
227
+ throw new ServerSynovaError(status, errorBody?.message);
228
+ }
229
+ if (errorBody) {
230
+ throw new ApiSynovaError(errorBody);
231
+ }
232
+ throw new ApiSynovaError({
233
+ code: "UNKNOWN_ERROR",
234
+ httpCode: status,
235
+ message: `HTTP ${status}: ${response.statusText}`,
236
+ requestId: "unknown",
237
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
238
+ });
239
+ }
240
+ buildUrl(path, query) {
241
+ const url = new URL(path, this.config.baseUrl);
242
+ if (query) {
243
+ for (const [key, value] of Object.entries(query)) {
244
+ if (value !== void 0) {
245
+ url.searchParams.set(key, value);
246
+ }
247
+ }
248
+ }
249
+ return url.toString();
250
+ }
251
+ isRetryable(error) {
252
+ if (error instanceof RateLimitSynovaError) return true;
253
+ if (error instanceof ServerSynovaError) return true;
254
+ if (error instanceof NetworkSynovaError) return true;
255
+ if (error instanceof TimeoutSynovaError) return true;
256
+ return false;
257
+ }
258
+ calculateRetryDelay(attempt, error) {
259
+ const { strategy, initialDelayMs, maxDelayMs, backoffMultiplier } = this.config.retry;
260
+ if (error instanceof RateLimitSynovaError && error.retryAfterMs) {
261
+ return Math.min(error.retryAfterMs, maxDelayMs);
262
+ }
263
+ let baseDelay;
264
+ if (strategy === "linear") {
265
+ baseDelay = initialDelayMs * attempt;
266
+ } else {
267
+ baseDelay = initialDelayMs * Math.pow(backoffMultiplier, attempt - 1);
268
+ }
269
+ const jitter = baseDelay * 0.1 * (Math.random() * 2 - 1);
270
+ const delay = baseDelay + jitter;
271
+ return Math.min(Math.max(delay, 0), maxDelayMs);
272
+ }
273
+ sleep(ms) {
274
+ return new Promise((resolve) => setTimeout(resolve, ms));
275
+ }
276
+ log(message) {
277
+ if (this.config.debug) {
278
+ this.config.logger.debug(`[Synova SDK] ${message}`);
279
+ }
280
+ }
281
+ };
282
+
283
+ // src/resources/prompts.ts
284
+ var PromptsResource = class {
285
+ constructor(http) {
286
+ this.http = http;
287
+ }
288
+ /**
289
+ * Get a prompt by ID (returns version with 'latest' tag)
290
+ *
291
+ * @param promptId - The prompt ID (e.g., 'prm_abc123')
292
+ * @param options - Optional settings
293
+ * @returns The prompt data
294
+ *
295
+ * @example
296
+ * ```ts
297
+ * // Get default (latest) version
298
+ * const prompt = await client.prompts.get('prm_abc123');
299
+ *
300
+ * // Get by specific tag
301
+ * const production = await client.prompts.get('prm_abc123', { tag: 'production' });
302
+ *
303
+ * // Get specific version
304
+ * const v2 = await client.prompts.get('prm_abc123', { version: '2.0.0' });
305
+ * ```
306
+ */
307
+ async get(promptId, options) {
308
+ if (options?.version) {
309
+ return this.http.request({
310
+ method: "GET",
311
+ path: `/api/v1/prompts/${promptId}/versions/${options.version}`
312
+ });
313
+ }
314
+ const tag = options?.tag || "latest";
315
+ if (tag === "latest") {
316
+ return this.http.request({
317
+ method: "GET",
318
+ path: `/api/v1/prompts/${promptId}`
319
+ });
320
+ }
321
+ return this.http.request({
322
+ method: "GET",
323
+ path: `/api/v1/prompts/${promptId}/tags/${tag}`
324
+ });
325
+ }
326
+ /**
327
+ * Execute a prompt with 'latest' tag
328
+ *
329
+ * @param promptId - The prompt ID
330
+ * @param options - Execution options including provider, model and variables
331
+ * @returns The execution response
332
+ *
333
+ * @example
334
+ * ```ts
335
+ * const result = await client.prompts.execute('prm_abc123', {
336
+ * provider: 'openai',
337
+ * model: 'gpt-4o',
338
+ * variables: { topic: 'TypeScript' },
339
+ * });
340
+ *
341
+ * if (result.type === 'message') {
342
+ * console.log(result.content);
343
+ * }
344
+ *
345
+ * // Image generation
346
+ * const imageResult = await client.prompts.execute('prm_image123', {
347
+ * provider: 'google',
348
+ * model: 'gemini-2.0-flash-exp',
349
+ * variables: { style: 'modern' },
350
+ * });
351
+ *
352
+ * if (imageResult.type === 'image') {
353
+ * console.log('Generated images:', imageResult.files);
354
+ * }
355
+ * ```
356
+ */
357
+ async execute(promptId, options) {
358
+ const body = {
359
+ provider: options.provider,
360
+ model: options.model
361
+ };
362
+ if (options.variables !== void 0) body.variables = options.variables;
363
+ if (options.messages !== void 0) body.messages = options.messages;
364
+ if (options.tag !== void 0) body.tag = options.tag;
365
+ if (options.version !== void 0) body.version = options.version;
366
+ if (options.metadata !== void 0) body.metadata = options.metadata;
367
+ if (options.parameters !== void 0) body.parameters = options.parameters;
368
+ if (options.responseSchema !== void 0) body.responseSchema = options.responseSchema;
369
+ return this.http.request({
370
+ method: "POST",
371
+ path: `/api/v1/prompts/${promptId}/run`,
372
+ body
373
+ });
374
+ }
375
+ /**
376
+ * Execute a prompt by tag
377
+ *
378
+ * @param promptId - The prompt ID
379
+ * @param tag - The tag (e.g., 'latest', 'production', 'staging')
380
+ * @param options - Execution options
381
+ * @returns The execution response
382
+ *
383
+ * @example
384
+ * ```ts
385
+ * const result = await client.prompts.executeByTag('prm_abc123', 'production', {
386
+ * provider: 'openai',
387
+ * model: 'gpt-4o',
388
+ * variables: { topic: 'TypeScript' },
389
+ * });
390
+ * ```
391
+ */
392
+ async executeByTag(promptId, tag, options) {
393
+ return this.http.request({
394
+ method: "POST",
395
+ path: `/api/v1/prompts/${promptId}/run`,
396
+ body: {
397
+ tag,
398
+ provider: options.provider,
399
+ model: options.model,
400
+ variables: options.variables,
401
+ messages: options.messages,
402
+ metadata: options.metadata,
403
+ parameters: options.parameters
404
+ }
405
+ });
406
+ }
407
+ /**
408
+ * Execute a prompt by version
409
+ *
410
+ * @param promptId - The prompt ID
411
+ * @param version - The semantic version (e.g., '1.0.0', '2.1.0')
412
+ * @param options - Execution options
413
+ * @returns The execution response
414
+ *
415
+ * @example
416
+ * ```ts
417
+ * const result = await client.prompts.executeByVersion('prm_abc123', '1.2.0', {
418
+ * provider: 'openai',
419
+ * model: 'gpt-4o',
420
+ * variables: { topic: 'TypeScript' },
421
+ * });
422
+ * ```
423
+ */
424
+ async executeByVersion(promptId, version, options) {
425
+ return this.http.request({
426
+ method: "POST",
427
+ path: `/api/v1/prompts/${promptId}/run`,
428
+ body: {
429
+ version,
430
+ provider: options.provider,
431
+ model: options.model,
432
+ variables: options.variables,
433
+ messages: options.messages,
434
+ metadata: options.metadata,
435
+ parameters: options.parameters
436
+ }
437
+ });
438
+ }
439
+ };
440
+
441
+ // src/resources/models.ts
442
+ var ModelsResource = class {
443
+ constructor(http) {
444
+ this.http = http;
445
+ }
446
+ /**
447
+ * List all available models
448
+ *
449
+ * @param options - Optional filters
450
+ * @returns List of providers with their models
451
+ *
452
+ * @example
453
+ * ```ts
454
+ * // Get all models
455
+ * const { providers } = await client.models.list();
456
+ *
457
+ * // Filter by type
458
+ * const imageModels = await client.models.list({ type: 'image' });
459
+ *
460
+ * // Filter by capability
461
+ * const functionCallingModels = await client.models.list({
462
+ * capability: 'function_calling',
463
+ * });
464
+ *
465
+ * // Filter by provider
466
+ * const openaiModels = await client.models.list({ provider: 'openai' });
467
+ * ```
468
+ */
469
+ async list(options) {
470
+ return this.http.request({
471
+ method: "GET",
472
+ path: "/api/v1/models",
473
+ query: {
474
+ type: options?.type,
475
+ capability: options?.capability,
476
+ provider: options?.provider
477
+ }
478
+ });
479
+ }
480
+ /**
481
+ * Get models for a specific provider
482
+ *
483
+ * @param provider - Provider ID (e.g., 'openai', 'anthropic')
484
+ * @returns List of models for the provider
485
+ *
486
+ * @example
487
+ * ```ts
488
+ * const { models } = await client.models.getByProvider('openai');
489
+ * ```
490
+ */
491
+ async getByProvider(provider) {
492
+ const response = await this.http.request({
493
+ method: "GET",
494
+ path: `/api/v1/models/${provider}`
495
+ });
496
+ return response.models;
497
+ }
498
+ /**
499
+ * Get a specific model
500
+ *
501
+ * @param provider - Provider ID
502
+ * @param model - Model ID
503
+ * @returns Model details
504
+ *
505
+ * @example
506
+ * ```ts
507
+ * const model = await client.models.get('openai', 'gpt-4o');
508
+ * console.log(model.capabilities);
509
+ * console.log(model.limits);
510
+ * ```
511
+ */
512
+ async get(provider, model) {
513
+ return this.http.request({
514
+ method: "GET",
515
+ path: `/api/v1/models/${provider}/${model}`
516
+ });
517
+ }
518
+ };
519
+
520
+ // src/resources/files.ts
521
+ var FilesResource = class {
522
+ constructor(http) {
523
+ this.http = http;
524
+ }
525
+ /**
526
+ * Upload files for use in prompt execution
527
+ *
528
+ * @param files - Array of File or Blob objects to upload
529
+ * @param options - Upload options including projectId
530
+ * @returns Upload response with file metadata
531
+ *
532
+ * @example
533
+ * ```ts
534
+ * // Upload files
535
+ * const result = await client.files.upload(
536
+ * [file1, file2],
537
+ * { projectId: 'prj_abc123' }
538
+ * );
539
+ *
540
+ * console.log('Uploaded files:', result.data);
541
+ *
542
+ * // Use uploaded file in prompt execution
543
+ * const response = await client.prompts.execute('prm_abc123', {
544
+ * provider: 'openai',
545
+ * model: 'gpt-4o',
546
+ * messages: [
547
+ * {
548
+ * role: 'user',
549
+ * content: 'Describe this image',
550
+ * files: [{ fileId: result.data[0].id }],
551
+ * },
552
+ * ],
553
+ * });
554
+ * ```
555
+ */
556
+ async upload(files, options) {
557
+ const formData = new FormData();
558
+ for (const file of files) {
559
+ formData.append("files", file);
560
+ }
561
+ formData.append("projectId", options.projectId);
562
+ return this.http.upload({
563
+ path: "/api/v1/files/upload",
564
+ formData
565
+ });
566
+ }
567
+ };
568
+
569
+ // src/client.ts
570
+ var DEFAULT_BASE_URL = "https://api.synova.cloud";
571
+ var DEFAULT_TIMEOUT = 3e4;
572
+ var DEFAULT_RETRY = {
573
+ maxRetries: 3,
574
+ strategy: "exponential",
575
+ initialDelayMs: 1e3,
576
+ maxDelayMs: 3e4,
577
+ backoffMultiplier: 2
578
+ };
579
+ var DEFAULT_LOGGER = {
580
+ debug: (message, ...args) => console.debug(message, ...args),
581
+ info: (message, ...args) => console.info(message, ...args),
582
+ warn: (message, ...args) => console.warn(message, ...args),
583
+ error: (messageOrError, ...args) => console.error(messageOrError, ...args)
584
+ };
585
+ var SynovaCloudSdk = class {
586
+ prompts;
587
+ models;
588
+ files;
589
+ http;
590
+ /**
591
+ * Create a new Synova Cloud SDK client
592
+ *
593
+ * @param apiKey - Your Synova API key
594
+ * @param config - Optional configuration
595
+ */
596
+ constructor(apiKey, config) {
597
+ if (!apiKey) {
598
+ throw new Error("API key is required");
599
+ }
600
+ this.http = new HttpClient({
601
+ baseUrl: config?.baseUrl ?? DEFAULT_BASE_URL,
602
+ apiKey,
603
+ timeout: config?.timeout ?? DEFAULT_TIMEOUT,
604
+ retry: {
605
+ maxRetries: config?.retry?.maxRetries ?? DEFAULT_RETRY.maxRetries,
606
+ strategy: config?.retry?.strategy ?? DEFAULT_RETRY.strategy,
607
+ initialDelayMs: config?.retry?.initialDelayMs ?? DEFAULT_RETRY.initialDelayMs,
608
+ maxDelayMs: config?.retry?.maxDelayMs ?? DEFAULT_RETRY.maxDelayMs,
609
+ backoffMultiplier: config?.retry?.backoffMultiplier ?? DEFAULT_RETRY.backoffMultiplier
610
+ },
611
+ debug: config?.debug ?? false,
612
+ logger: config?.logger ?? DEFAULT_LOGGER
613
+ });
614
+ this.prompts = new PromptsResource(this.http);
615
+ this.models = new ModelsResource(this.http);
616
+ this.files = new FilesResource(this.http);
617
+ }
618
+ };
619
+
620
+ exports.ApiSynovaError = ApiSynovaError;
621
+ exports.AuthSynovaError = AuthSynovaError;
622
+ exports.NetworkSynovaError = NetworkSynovaError;
623
+ exports.NotFoundSynovaError = NotFoundSynovaError;
624
+ exports.RateLimitSynovaError = RateLimitSynovaError;
625
+ exports.ServerSynovaError = ServerSynovaError;
626
+ exports.SynovaCloudSdk = SynovaCloudSdk;
627
+ exports.SynovaError = SynovaError;
628
+ exports.TimeoutSynovaError = TimeoutSynovaError;
629
+ //# sourceMappingURL=index.cjs.map
630
+ //# sourceMappingURL=index.cjs.map