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