@sume-com/client 0.0.0-stage → 0.3.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,379 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createSumeClient = createSumeClient;
7
+ exports.configure = configure;
8
+ exports.reset = reset;
9
+ const axios_1 = __importDefault(require("axios"));
10
+ const crypto_1 = require("crypto");
11
+ const config_1 = require("../config");
12
+ const auth_1 = require("../auth");
13
+ const errors_1 = require("../errors");
14
+ const retry_1 = require("../utils/retry");
15
+ const version_1 = require("../version");
16
+ const globalState = {};
17
+ const TERMINAL_STATUSES = new Set([
18
+ 'completed',
19
+ 'failed',
20
+ 'canceled',
21
+ ]);
22
+ const IDEMPOTENCY_KEY = /^[\x20-\x7E]{1,255}$/;
23
+ const MODEL_ID = /^[A-Za-z0-9][\w.-]*$/;
24
+ const ROUTER_BY_KIND = {
25
+ image: '/v1/image-router/generate',
26
+ video: '/v1/video-router/generate',
27
+ lipsync: '/v1/video-router/generate',
28
+ captions: '/v1/video-router/generate',
29
+ };
30
+ function checkBrowserEnvironment() {
31
+ const isBrowser = typeof globalThis.window !== 'undefined';
32
+ if (isBrowser) {
33
+ throw new errors_1.BrowserNotSupportedError();
34
+ }
35
+ }
36
+ function parseApiKey(value) {
37
+ const key = value.trim();
38
+ if (key === '' || /\s|:/.test(key)) {
39
+ throw new errors_1.BadInputError('Credentials must be one Sume API key ("sume_live_..."), not "KEY_ID:KEY_SECRET"');
40
+ }
41
+ return key;
42
+ }
43
+ function validationDetail(body) {
44
+ const errors = body?.details?.errors;
45
+ if (!Array.isArray(errors) || errors.length === 0) {
46
+ return undefined;
47
+ }
48
+ return errors.map((err) => ({
49
+ type: String(err?.type ?? 'invalid'),
50
+ loc: Array.isArray(err?.loc)
51
+ ? err.loc.map(String)
52
+ : typeof err?.path === 'string' && err.path !== ''
53
+ ? err.path.split('.')
54
+ : [],
55
+ msg: String(err?.message ?? err?.msg ?? 'Invalid value'),
56
+ }));
57
+ }
58
+ function mapAPIError(error) {
59
+ const response = error.response;
60
+ if (!response) {
61
+ const wrapped = new errors_1.APIError(`Request to the Sume API failed: ${error.message}`);
62
+ wrapped.code = error.code;
63
+ throw wrapped;
64
+ }
65
+ const data = response.data;
66
+ const body = data && typeof data === 'object' ? data.error : undefined;
67
+ const message = typeof body?.message === 'string' && body.message !== '' ? body.message : error.message;
68
+ if (response.status === 401) {
69
+ throw new errors_1.AuthenticationError(message, data);
70
+ }
71
+ else if (response.status === 402) {
72
+ throw new errors_1.NotEnoughCreditsError(message, data);
73
+ }
74
+ else if (response.status === 422) {
75
+ throw new errors_1.ValidationError(validationDetail(body) ?? message, data);
76
+ }
77
+ else if (response.status === 400) {
78
+ throw new errors_1.BadInputError(validationDetail(body) ?? message, data);
79
+ }
80
+ throw new errors_1.APIError(message, response.status, data, (0, retry_1.parseRetryAfter)(response.headers?.['retry-after']));
81
+ }
82
+ function mapStorageError(error) {
83
+ const status = error.response?.status;
84
+ const wrapped = new errors_1.APIError(status
85
+ ? `Upload to storage failed with status ${status}`
86
+ : `Upload to storage failed: ${error.message}`, status, undefined, (0, retry_1.parseRetryAfter)(error.response?.headers?.['retry-after']));
87
+ if (!status) {
88
+ wrapped.code = error.code;
89
+ }
90
+ throw wrapped;
91
+ }
92
+ function initializeClient(config) {
93
+ checkBrowserEnvironment();
94
+ let apiKey;
95
+ if (config?.credentials) {
96
+ apiKey = parseApiKey(config.credentials);
97
+ }
98
+ else if (config?.apiKey) {
99
+ apiKey = parseApiKey(config.apiKey);
100
+ }
101
+ const cfg = new config_1.Config(config);
102
+ let creds;
103
+ if (apiKey) {
104
+ creds = { apiKey };
105
+ }
106
+ else {
107
+ try {
108
+ creds = (0, auth_1.fetchCredentials)();
109
+ }
110
+ catch {
111
+ creds = { apiKey: '' };
112
+ }
113
+ }
114
+ const headers = {
115
+ Authorization: `Bearer ${creds.apiKey}`,
116
+ 'Content-Type': 'application/json',
117
+ };
118
+ if (typeof process !== 'undefined' && process.versions?.node) {
119
+ headers['User-Agent'] = `sume-server-js/${version_1.VERSION}`;
120
+ }
121
+ Object.assign(headers, cfg.headers);
122
+ const axiosClient = axios_1.default.create({
123
+ baseURL: cfg.baseURL,
124
+ timeout: cfg.timeout,
125
+ headers,
126
+ });
127
+ axiosClient.interceptors.response.use((response) => response, mapAPIError);
128
+ return { config: cfg, client: axiosClient, credentials: creds, routes: new Map() };
129
+ }
130
+ function retryConfig(config) {
131
+ return {
132
+ maxRetries: config.maxRetries,
133
+ backoff: config.retryBackoff,
134
+ maxBackoff: config.retryMaxBackoff,
135
+ };
136
+ }
137
+ function unwrap(body) {
138
+ return body && typeof body === 'object' && body.data && typeof body.data === 'object'
139
+ ? body.data
140
+ : body;
141
+ }
142
+ function requestIdOf(payload) {
143
+ const id = payload?.request_id ?? payload?.job_id ?? payload?.job?.id;
144
+ if (typeof id !== 'string' || id === '') {
145
+ throw new errors_1.APIError('The Sume API answered without a request id', undefined, payload);
146
+ }
147
+ return id;
148
+ }
149
+ function statusOf(payload) {
150
+ const raw = payload?.sume_status ?? payload?.job?.status ?? payload?.status;
151
+ switch (String(raw ?? '').toLowerCase()) {
152
+ case 'queued':
153
+ case 'in_queue':
154
+ return 'queued';
155
+ case 'processing':
156
+ case 'in_progress':
157
+ return 'in_progress';
158
+ case 'completed':
159
+ return 'completed';
160
+ case 'failed':
161
+ return 'failed';
162
+ case 'canceled':
163
+ case 'cancelled':
164
+ return 'canceled';
165
+ default:
166
+ return payload?.terminal === true ? 'failed' : 'in_progress';
167
+ }
168
+ }
169
+ function isTerminal(payload) {
170
+ return payload?.terminal === true || TERMINAL_STATUSES.has(statusOf(payload));
171
+ }
172
+ function jobPath(requestId, suffix = '') {
173
+ return `/v1/jobs/${encodeURIComponent(requestId)}${suffix}`;
174
+ }
175
+ function jobUrl(config, requestId, suffix) {
176
+ return `${config.baseURL.replace(/\/+$/, '')}${jobPath(requestId, suffix)}`;
177
+ }
178
+ function nextPollDelay(config, payload) {
179
+ const hint = Number(payload?.next_poll_after_seconds);
180
+ return Math.max(config.pollInterval, Number.isFinite(hint) && hint > 0 ? hint * 1000 : 0);
181
+ }
182
+ function toV2Response(config, payload, artifacts, error) {
183
+ const requestId = requestIdOf(payload);
184
+ const status = statusOf(payload);
185
+ const response = {
186
+ status: status === 'failed' && error?.public_reason === 'content_policy_rejected' ? 'nsfw' : status,
187
+ request_id: requestId,
188
+ status_url: payload.status_url ?? jobUrl(config, requestId, '/status'),
189
+ cancel_url: payload.cancel_url ?? null,
190
+ result_url: payload.result_url ?? jobUrl(config, requestId, '/result'),
191
+ };
192
+ if (artifacts) {
193
+ const images = artifacts.filter((a) => a.type === 'image').map((a) => ({ url: a.url }));
194
+ const video = artifacts.find((a) => a.type === 'video');
195
+ if (images.length > 0) {
196
+ response.images = images;
197
+ }
198
+ if (video) {
199
+ response.video = { url: video.url };
200
+ }
201
+ response.artifacts = artifacts;
202
+ }
203
+ if (error !== undefined) {
204
+ response.error = error;
205
+ }
206
+ return response;
207
+ }
208
+ async function pollV2Request(client, config, submitted, idempotencyKey) {
209
+ const requestId = requestIdOf(submitted);
210
+ const pollingUrl = jobPath(requestId, '/status');
211
+ const deadline = Date.now() + config.maxPollTime;
212
+ let delay = nextPollDelay(config, submitted);
213
+ while (true) {
214
+ const remaining = deadline - Date.now();
215
+ if (remaining <= 0) {
216
+ throw new errors_1.TimeoutError(`Polling exceeded maximum time of ${config.maxPollTime}ms for request ${requestId}. ` +
217
+ `The job keeps running: call subscribe again with idempotencyKey "${idempotencyKey}" to keep waiting for it.`, requestId, idempotencyKey);
218
+ }
219
+ await (0, retry_1.sleep)(Math.min(delay, remaining));
220
+ try {
221
+ const response = await client.get(pollingUrl);
222
+ const payload = unwrap(response.data);
223
+ if (isTerminal(payload)) {
224
+ return payload;
225
+ }
226
+ delay = nextPollDelay(config, payload);
227
+ }
228
+ catch (error) {
229
+ if (!(0, retry_1.isRetryableError)(error)) {
230
+ throw error;
231
+ }
232
+ delay = Math.max(config.pollInterval, (0, retry_1.retryAfterMs)(error) ?? 0);
233
+ }
234
+ }
235
+ }
236
+ async function finishRequest(client, config, payload) {
237
+ const requestId = requestIdOf(payload);
238
+ if (statusOf(payload) === 'completed') {
239
+ const response = await (0, retry_1.retryWithBackoff)(() => client.get(jobPath(requestId, '/result')), retryConfig(config));
240
+ const artifacts = unwrap(response.data)?.result?.artifacts;
241
+ return toV2Response(config, payload, Array.isArray(artifacts) ? artifacts : []);
242
+ }
243
+ let error = payload.job && typeof payload.job === 'object' && 'error' in payload.job
244
+ ? payload.job.error
245
+ : payload.error;
246
+ if (error === undefined) {
247
+ const response = await (0, retry_1.retryWithBackoff)(() => client.get(jobPath(requestId)), retryConfig(config));
248
+ error = unwrap(response.data)?.job?.error ?? null;
249
+ }
250
+ return toV2Response(config, payload, undefined, error);
251
+ }
252
+ async function resolveEndpoint(state, endpoint) {
253
+ if (endpoint.includes('/')) {
254
+ return { path: endpoint.startsWith('/') ? endpoint : `/${endpoint}` };
255
+ }
256
+ if (!MODEL_ID.test(endpoint)) {
257
+ throw new errors_1.BadInputError('Endpoint must be a model id, such as "gpt-image-2", or a path, such as "/v1/image-router/generate"');
258
+ }
259
+ const cached = state.routes?.get(endpoint);
260
+ if (cached) {
261
+ return { path: cached, model: endpoint };
262
+ }
263
+ const response = await (0, retry_1.retryWithBackoff)(() => state.client.get(`/v1/open/catalog/${encodeURIComponent(endpoint)}`), retryConfig(state.config));
264
+ const kind = unwrap(response.data)?.kind;
265
+ const path = typeof kind === 'string' ? ROUTER_BY_KIND[kind] : undefined;
266
+ if (!path) {
267
+ throw new errors_1.BadInputError(`Model "${endpoint}" is of kind "${String(kind)}", which this SDK cannot route. ` +
268
+ `Call subscribe('/v1/<router>/generate', { input: { model: '${endpoint}', ... } }).`);
269
+ }
270
+ (state.routes ?? (state.routes = new Map())).set(endpoint, path);
271
+ return { path, model: endpoint };
272
+ }
273
+ function toBuffer(data) {
274
+ if (Buffer.isBuffer(data)) {
275
+ return data;
276
+ }
277
+ if (data instanceof Uint8Array) {
278
+ return Buffer.from(data.buffer, data.byteOffset, data.byteLength);
279
+ }
280
+ throw new errors_1.BadInputError('Upload data must be a Buffer or Uint8Array');
281
+ }
282
+ function createSumeClient(config) {
283
+ const state = config ? initializeClient(config) : globalState;
284
+ function ensureInitialized() {
285
+ checkBrowserEnvironment();
286
+ if (!state.client || !state.credentials?.apiKey) {
287
+ const initialized = initializeClient(state.config);
288
+ if (!initialized.credentials.apiKey) {
289
+ throw new errors_1.CredentialsMissedError();
290
+ }
291
+ Object.assign(state, initialized);
292
+ }
293
+ }
294
+ async function upload(data, contentType) {
295
+ ensureInitialized();
296
+ const client = state.client;
297
+ const clientConfig = state.config;
298
+ if (typeof contentType !== 'string' || contentType.trim() === '') {
299
+ throw new errors_1.BadInputError('Content type must be a MIME type, such as "image/png"');
300
+ }
301
+ const bytes = toBuffer(data);
302
+ if (bytes.byteLength === 0) {
303
+ throw new errors_1.BadInputError('Upload data is empty');
304
+ }
305
+ const linkResponse = await (0, retry_1.retryWithBackoff)(() => client.post('/v1/files/generate-upload-url', {
306
+ content_type: contentType,
307
+ size_bytes: bytes.byteLength,
308
+ }), retryConfig(clientConfig));
309
+ const link = unwrap(linkResponse.data);
310
+ const storage = axios_1.default.create();
311
+ storage.interceptors.response.use((response) => response, mapStorageError);
312
+ await (0, retry_1.retryWithBackoff)(() => storage.request({
313
+ method: link.upload_method || 'PUT',
314
+ url: link.upload_url,
315
+ data: bytes,
316
+ headers: link.upload_headers ?? { 'Content-Type': link.content_type ?? contentType },
317
+ maxBodyLength: Infinity,
318
+ maxContentLength: Infinity,
319
+ timeout: clientConfig.timeout,
320
+ }), retryConfig(clientConfig));
321
+ return link.public_url;
322
+ }
323
+ return {
324
+ async subscribe(endpoint, options) {
325
+ ensureInitialized();
326
+ const client = state.client;
327
+ const clientConfig = state.config;
328
+ if (typeof endpoint !== 'string' || endpoint.trim() === '') {
329
+ throw new errors_1.BadInputError('Endpoint must be a model id, such as "gpt-image-2", or a path, such as "/v1/image-router/generate"');
330
+ }
331
+ if (!options || typeof options.input !== 'object' || options.input === null) {
332
+ throw new errors_1.BadInputError('options.input must be an object');
333
+ }
334
+ const inputModel = options.input.model;
335
+ if (!endpoint.includes('/') && inputModel !== undefined && inputModel !== endpoint) {
336
+ throw new errors_1.BadInputError(`input.model "${String(inputModel)}" does not match the endpoint model "${endpoint}"`);
337
+ }
338
+ const { input, webhook, withPolling = true } = options;
339
+ const idempotencyKey = options.idempotencyKey ?? (0, crypto_1.randomUUID)();
340
+ if (!IDEMPOTENCY_KEY.test(idempotencyKey)) {
341
+ throw new errors_1.BadInputError('idempotencyKey must be 1 to 255 printable ASCII characters');
342
+ }
343
+ const { path, model } = await resolveEndpoint(state, endpoint);
344
+ const requestBody = model ? { ...input, model } : { ...input };
345
+ if (webhook) {
346
+ requestBody.mode = 'webhook';
347
+ requestBody.webhook_url = webhook.url;
348
+ }
349
+ const response = await (0, retry_1.retryWithBackoff)(() => client.post(path, requestBody, {
350
+ headers: { 'Idempotency-Key': idempotencyKey },
351
+ }), retryConfig(clientConfig));
352
+ let payload = unwrap(response.data);
353
+ if (!isTerminal(payload)) {
354
+ if (!withPolling) {
355
+ return toV2Response(clientConfig, payload);
356
+ }
357
+ payload = await pollV2Request(client, clientConfig, payload, idempotencyKey);
358
+ }
359
+ return finishRequest(client, clientConfig, payload);
360
+ },
361
+ upload,
362
+ async uploadImage(imageBuffer, format = 'jpeg') {
363
+ return upload(imageBuffer, `image/${format}`);
364
+ },
365
+ configure(config) {
366
+ checkBrowserEnvironment();
367
+ Object.assign(state, initializeClient(config));
368
+ },
369
+ };
370
+ }
371
+ function configure(config) {
372
+ Object.assign(globalState, initializeClient(config));
373
+ }
374
+ function reset() {
375
+ globalState.config = undefined;
376
+ globalState.client = undefined;
377
+ globalState.credentials = undefined;
378
+ globalState.routes = undefined;
379
+ }
@@ -0,0 +1,12 @@
1
+ import { createSumeClient, configure as configureClient, V2ClientConfig, SumeClient, SubscribeOptions, SubscribeInput } from './client';
2
+ export declare const sume: SumeClient;
3
+ export declare function config(config: V2ClientConfig): void;
4
+ export { configureClient as configure, createSumeClient };
5
+ export type { V2ClientConfig, SumeClient, SubscribeOptions, SubscribeInput };
6
+ export type { ClientConfig } from '../config';
7
+ export { DEFAULT_BASE_URL } from '../config';
8
+ export { VERSION } from '../version';
9
+ export * from '../types';
10
+ export * from '../errors';
11
+ export * from '../helpers';
12
+ export * from './types';
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.VERSION = exports.DEFAULT_BASE_URL = exports.createSumeClient = exports.configure = exports.sume = void 0;
18
+ exports.config = config;
19
+ const client_1 = require("./client");
20
+ Object.defineProperty(exports, "createSumeClient", { enumerable: true, get: function () { return client_1.createSumeClient; } });
21
+ Object.defineProperty(exports, "configure", { enumerable: true, get: function () { return client_1.configure; } });
22
+ exports.sume = (0, client_1.createSumeClient)(undefined);
23
+ function config(config) {
24
+ (0, client_1.configure)(config);
25
+ }
26
+ var config_1 = require("../config");
27
+ Object.defineProperty(exports, "DEFAULT_BASE_URL", { enumerable: true, get: function () { return config_1.DEFAULT_BASE_URL; } });
28
+ var version_1 = require("../version");
29
+ Object.defineProperty(exports, "VERSION", { enumerable: true, get: function () { return version_1.VERSION; } });
30
+ __exportStar(require("../types"), exports);
31
+ __exportStar(require("../errors"), exports);
32
+ __exportStar(require("../helpers"), exports);
33
+ __exportStar(require("./types"), exports);
@@ -0,0 +1,58 @@
1
+ import { Artifact, JobError } from '../types';
2
+ export interface ImageGenerateInput {
3
+ model: string;
4
+ prompt: string;
5
+ image_urls?: string[];
6
+ aspect_ratio?: string;
7
+ image_size?: string | {
8
+ width: number;
9
+ height: number;
10
+ };
11
+ quality?: string;
12
+ resolution?: string;
13
+ num_images?: number;
14
+ output_format?: 'png' | 'jpeg' | 'webp';
15
+ mask_url?: string;
16
+ metadata?: Record<string, unknown>;
17
+ [field: string]: unknown;
18
+ }
19
+ export interface VideoGenerateInput {
20
+ model: string;
21
+ prompt?: string;
22
+ duration?: number;
23
+ resolution?: string;
24
+ aspect_ratio?: string;
25
+ image_url?: string;
26
+ end_image_url?: string;
27
+ video_url?: string;
28
+ audio_url?: string;
29
+ reference_image_urls?: string[];
30
+ reference_video_urls?: string[];
31
+ reference_audio_urls?: string[];
32
+ generate_audio?: boolean;
33
+ metadata?: Record<string, unknown>;
34
+ [field: string]: unknown;
35
+ }
36
+ export interface EndpointInputMap {
37
+ '/v1/image-router/generate': ImageGenerateInput;
38
+ '/v1/video-router/generate': VideoGenerateInput;
39
+ }
40
+ export type EndpointInput<TEndpoint extends keyof EndpointInputMap> = EndpointInputMap[TEndpoint];
41
+ export type V2RequestStatus = 'queued' | 'in_progress' | 'completed' | 'failed' | 'nsfw' | 'canceled';
42
+ export interface V2Image {
43
+ url: string;
44
+ }
45
+ export interface V2Video {
46
+ url: string;
47
+ }
48
+ export interface V2Response {
49
+ status: V2RequestStatus;
50
+ request_id: string;
51
+ status_url: string;
52
+ cancel_url: string | null;
53
+ result_url: string;
54
+ images?: V2Image[];
55
+ video?: V2Video;
56
+ artifacts?: Artifact[];
57
+ error?: JobError | null;
58
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1 @@
1
+ export declare const VERSION = "0.3.0";
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VERSION = void 0;
4
+ exports.VERSION = '0.3.0';
package/package.json CHANGED
@@ -1,6 +1,81 @@
1
1
  {
2
2
  "name": "@sume-com/client",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.3.0",
4
+ "description": "Official Sume SDK for Node.js and TypeScript",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ },
12
+ "./v2": {
13
+ "types": "./dist/v2/index.d.ts",
14
+ "default": "./dist/v2/index.js"
15
+ },
16
+ "./package.json": "./package.json"
17
+ },
18
+ "typesVersions": {
19
+ "*": {
20
+ "v2": [
21
+ "./dist/v2/index.d.ts"
22
+ ]
23
+ }
24
+ },
25
+ "files": [
26
+ "dist/**/*",
27
+ "README.md",
28
+ "LICENSE",
29
+ "THIRD-PARTY-NOTICES.txt"
30
+ ],
31
+ "scripts": {
32
+ "build": "tsc",
33
+ "build:watch": "tsc --watch",
34
+ "build:prod": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.prod.json",
35
+ "test": "jest --passWithNoTests",
36
+ "test:watch": "jest --watch --passWithNoTests",
37
+ "lint": "eslint src",
38
+ "format": "prettier --write \"src/**/*.ts\"",
39
+ "format:check": "prettier --check \"src/**/*.ts\"",
40
+ "prepare": "npm run build:prod",
41
+ "prepublishOnly": "npm run build:prod && npm run test"
42
+ },
43
+ "keywords": [
44
+ "sume",
45
+ "open.sume.com",
46
+ "ai",
47
+ "api",
48
+ "client",
49
+ "sdk",
50
+ "typescript",
51
+ "image-generation",
52
+ "video-generation"
53
+ ],
54
+ "author": "Sume Labs",
55
+ "license": "MIT",
56
+ "repository": {
57
+ "type": "git",
58
+ "url": "git+https://github.com/sumelabs/sume-js.git"
59
+ },
60
+ "bugs": {
61
+ "url": "https://github.com/sumelabs/sume-js/issues"
62
+ },
63
+ "homepage": "https://open.sume.com/docs",
64
+ "engines": {
65
+ "node": ">=18.0.0"
66
+ },
67
+ "dependencies": {
68
+ "axios": "^1.20.0"
69
+ },
70
+ "devDependencies": {
71
+ "@eslint/js": "^10.0.1",
72
+ "@types/jest": "^30.0.0",
73
+ "@types/node": "^20.19.0",
74
+ "eslint": "^10.13.0",
75
+ "jest": "^30.5.2",
76
+ "prettier": "^3.9.10",
77
+ "ts-jest": "^29.4.15",
78
+ "typescript": "~5.9.3",
79
+ "typescript-eslint": "^8.72.0"
80
+ }
81
+ }