@sogni-ai/sogni-client 5.41.0 → 5.42.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/AGENTS.md CHANGED
@@ -27,6 +27,7 @@ Current public API anchors:
27
27
  - Socket-native LLM chat uses `sogni.chat.completions.create()`.
28
28
  - Durable creative workflows use `sogni.workflows`.
29
29
  - Project cost helpers are `sogni.projects.estimateCost()`, `estimateVideoCost()`, and `estimateAudioCost()`.
30
+ - `sogni.projects.assets` manages private subscriber uploads (`upload`, `list`, `remove`, `bind`). Supported servers automatically reuse matching files passed to `projects.create()`; saved IDs do not replace file parameters.
30
31
  - `checkAuth()` is only for cookie-auth browser flows. API-key auth auto-authenticates during `createInstance()`, and token auth uses `login()` or `setTokens()`.
31
32
  - `ChatCompletionResult` is SDK-shaped (`content`, `role`, `finishReason`, `tool_calls`, `usage`, `cost`). Streaming chunks expose `chunk.content` and optional `chunk.tool_calls`.
32
33
 
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # [5.42.0](https://github.com/Sogni-AI/sogni-client/compare/v5.41.0...v5.42.0) (2026-09-11)
2
+
3
+
4
+ ### Features
5
+
6
+ * **projects:** reuse private subscriber uploads across projects ([938e8a4](https://github.com/Sogni-AI/sogni-client/commit/938e8a4d9716706530be4586ee324490f6374b01))
7
+
1
8
  # [5.41.0](https://github.com/Sogni-AI/sogni-client/compare/v5.40.0...v5.41.0) (2026-09-11)
2
9
 
3
10
 
package/README.md CHANGED
@@ -749,6 +749,34 @@ const project = await sogni.projects.create({
749
749
  - Workers download a LoRA on first use, so the first render with an uncached one
750
750
  takes longer to start.
751
751
 
752
+ ### Reusable subscriber uploads
753
+
754
+ On servers that support saved uploads, eligible subscribers can reuse the same
755
+ image, video or audio file across projects. Pass files to `projects.create()` as
756
+ usual: the SDK checks for a previously saved copy before transferring bytes.
757
+ Uploads remain private to the signed-in account. Older servers and accounts
758
+ without this feature continue using ordinary project uploads.
759
+
760
+ ```javascript
761
+ const saved = await sogni.projects.assets.upload(file, file.type, 'Product reference');
762
+ const { assets, limits } = await sogni.projects.assets.list();
763
+ console.log(saved.id, assets, limits);
764
+ // Reusing the same file in later projects needs no repeat upload.
765
+ // Removal does not remove inputs already copied into an existing project.
766
+ await sogni.projects.assets.remove(saved.id);
767
+ ```
768
+
769
+ Use the returned limits and `expiresAt` to display remaining storage and expiry.
770
+ An expired entry can be removed even after the subscription ends. Explicit
771
+ `assets.upload()` calls report errors; automatic project uploads fall back only
772
+ when saved storage cannot be prepared. Transfer or verification failures stop
773
+ project submission. Saved-upload IDs are not accepted in place of files in
774
+ `projects.create()`; `assets.bind(id, { projectId, type, id? })` is available for
775
+ clients that manage project IDs and input slots directly.
776
+
777
+ Project history may include `byolUsed`, `personalLoras` public-source snapshots,
778
+ and `reusedAssetCount`. Missing fields on older projects mean unknown, not zero.
779
+
752
780
  ### ControlNets
753
781
 
754
782
  **EXPERIMENTAL FEATURE:** This feature is still in development and may not work as expected. Use at your own risk.
@@ -0,0 +1,47 @@
1
+ import type RestClient from '../lib/RestClient.js';
2
+ export interface SavedUpload {
3
+ id: string;
4
+ name: string;
5
+ bytes: number;
6
+ contentType: string;
7
+ state: 'uploading' | 'ready';
8
+ createdAt: number;
9
+ expiresAt: number;
10
+ }
11
+ export interface SavedUploadBinding {
12
+ projectId: string;
13
+ type: string;
14
+ id?: string;
15
+ }
16
+ /** Private subscriber uploads that can be reused across projects. */
17
+ export default class ReusableUploads {
18
+ private readonly rest;
19
+ private pending;
20
+ private lanes;
21
+ private nextLane;
22
+ private session;
23
+ private availability?;
24
+ private preparationFailures;
25
+ constructor(rest: RestClient);
26
+ private assertSession;
27
+ private canAutomaticallySave;
28
+ list(): Promise<{
29
+ assets: SavedUpload[];
30
+ limits: {
31
+ entries: number;
32
+ bytes: number;
33
+ fileBytes: number;
34
+ idleDays: number;
35
+ copyBytesPerDay: number;
36
+ bindingsPerDay: number;
37
+ };
38
+ }>;
39
+ remove(id: string): Promise<void>;
40
+ bind(id: string, binding: SavedUploadBinding): Promise<void>;
41
+ private retryBusy;
42
+ /** Upload once; the API verifies the file before making it reusable. */
43
+ upload(file: Blob | Buffer, contentType: string, name?: string): Promise<SavedUpload>;
44
+ private performUpload;
45
+ /** Existing project uploads remain available when saved uploads cannot be used. */
46
+ tryBindFile(file: Blob | Buffer, contentType: string | undefined, binding: SavedUploadBinding): Promise<boolean>;
47
+ }
@@ -0,0 +1,210 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const index_js_1 = require("../ApiClient/index.js");
13
+ const SUPPORTED_TYPES = new Set([
14
+ 'image/png',
15
+ 'image/jpeg',
16
+ 'image/webp',
17
+ 'video/mp4',
18
+ 'video/quicktime',
19
+ 'video/webm',
20
+ 'audio/mp4',
21
+ 'audio/mpeg',
22
+ 'audio/flac',
23
+ 'audio/wav',
24
+ 'audio/x-wav',
25
+ 'audio/wave'
26
+ ]);
27
+ /** Private subscriber uploads that can be reused across projects. */
28
+ class ReusableUploads {
29
+ constructor(rest) {
30
+ var _a;
31
+ this.rest = rest;
32
+ this.pending = new Map();
33
+ // Bound hashing/upload memory even when a project has many reference files.
34
+ this.lanes = [Promise.resolve(), Promise.resolve()];
35
+ this.nextLane = 0;
36
+ this.session = 0;
37
+ this.preparationFailures = new WeakSet();
38
+ (_a = rest.auth) === null || _a === void 0 ? void 0 : _a.on('updated', () => {
39
+ this.session += 1;
40
+ this.pending.clear();
41
+ this.availability = undefined;
42
+ });
43
+ }
44
+ assertSession(session) {
45
+ if (session !== this.session)
46
+ throw new Error('The account changed. Select the upload again.');
47
+ }
48
+ canAutomaticallySave() {
49
+ if (this.availability && this.availability.expiresAt > Date.now())
50
+ return this.availability.result;
51
+ const result = this.rest
52
+ .get('/v1/assets/capabilities')
53
+ .then((response) => response.data.enabled === true)
54
+ .catch((error) => {
55
+ if (error instanceof index_js_1.ApiError && [403, 404, 503].includes(error.status))
56
+ return false;
57
+ this.availability = undefined;
58
+ throw error;
59
+ });
60
+ this.availability = { expiresAt: Date.now() + 60000, result };
61
+ return result;
62
+ }
63
+ list() {
64
+ return __awaiter(this, void 0, void 0, function* () {
65
+ const result = yield this.rest.get('/v1/assets');
66
+ return result.data;
67
+ });
68
+ }
69
+ remove(id) {
70
+ return __awaiter(this, void 0, void 0, function* () {
71
+ yield this.rest.delete(`/v1/assets/${encodeURIComponent(id)}`);
72
+ });
73
+ }
74
+ bind(id, binding) {
75
+ return __awaiter(this, void 0, void 0, function* () {
76
+ const session = this.session;
77
+ yield this.retryBusy(() => {
78
+ this.assertSession(session);
79
+ return this.rest.post(`/v1/assets/${encodeURIComponent(id)}/bind`, Object.assign({}, binding));
80
+ });
81
+ this.assertSession(session);
82
+ });
83
+ }
84
+ retryBusy(operation) {
85
+ return __awaiter(this, void 0, void 0, function* () {
86
+ for (let attempt = 0;; attempt += 1) {
87
+ try {
88
+ return yield operation();
89
+ }
90
+ catch (error) {
91
+ if (!(error instanceof index_js_1.ApiError) || error.status !== 423 || attempt >= 4)
92
+ throw error;
93
+ yield new Promise((resolve) => setTimeout(resolve, 250 * Math.pow(2, attempt)));
94
+ }
95
+ }
96
+ });
97
+ }
98
+ /** Upload once; the API verifies the file before making it reusable. */
99
+ upload(file, contentType, name = 'Saved upload') {
100
+ const session = this.session;
101
+ const size = file instanceof Blob ? file.size : file.byteLength;
102
+ if (!size || size > 100 * 1024 * 1024)
103
+ return Promise.reject(new index_js_1.ApiError(400, {
104
+ status: 'error',
105
+ errorCode: 0,
106
+ message: 'Choose a saved upload no larger than 100 MiB.'
107
+ }));
108
+ const lane = this.nextLane++ % this.lanes.length;
109
+ const run = this.lanes[lane].then(() => __awaiter(this, void 0, void 0, function* () {
110
+ this.assertSession(session);
111
+ const bytes = file instanceof Blob ? yield file.arrayBuffer() : file;
112
+ const digest = yield globalThis.crypto.subtle.digest('SHA-256', bytes);
113
+ this.assertSession(session);
114
+ const sha256 = Array.from(new Uint8Array(digest), (value) => value.toString(16).padStart(2, '0')).join('');
115
+ const key = `${sha256}:${contentType}`;
116
+ const existing = this.pending.get(key);
117
+ if (existing)
118
+ return existing;
119
+ const task = this.performUpload(file, bytes.byteLength, sha256, contentType, name, session);
120
+ this.pending.set(key, task);
121
+ try {
122
+ return yield task;
123
+ }
124
+ finally {
125
+ if (this.pending.get(key) === task)
126
+ this.pending.delete(key);
127
+ }
128
+ }));
129
+ this.lanes[lane] = run.catch(() => undefined);
130
+ return run;
131
+ }
132
+ performUpload(file, bytes, sha256, contentType, name, session) {
133
+ return __awaiter(this, void 0, void 0, function* () {
134
+ const result = yield this.retryBusy(() => {
135
+ this.assertSession(session);
136
+ return this.rest.post('/v1/assets/prepare', {
137
+ sha256,
138
+ bytes,
139
+ contentType,
140
+ name
141
+ });
142
+ }).catch((error) => {
143
+ if (error instanceof Error)
144
+ this.preparationFailures.add(error);
145
+ throw error;
146
+ });
147
+ this.assertSession(session);
148
+ const prepared = result.data;
149
+ if (prepared.state === 'ready')
150
+ return prepared;
151
+ if (!prepared.uploadUrl || !prepared.uploadHeaders)
152
+ throw new Error('The saved upload could not be prepared.');
153
+ const body = file instanceof Blob ? file : new Blob([new Uint8Array(file)]);
154
+ const response = yield fetch(prepared.uploadUrl, {
155
+ method: 'PUT',
156
+ body,
157
+ headers: prepared.uploadHeaders,
158
+ redirect: 'error',
159
+ credentials: 'omit',
160
+ signal: AbortSignal.timeout(300000)
161
+ });
162
+ this.assertSession(session);
163
+ // A concurrent/retried write-once upload may already have finished. The
164
+ // server still verifies the stored bytes before acknowledging completion.
165
+ if (!response.ok && response.status !== 412)
166
+ throw new index_js_1.ApiError(response.status, {
167
+ status: 'error',
168
+ errorCode: 0,
169
+ message: 'Could not upload the selected file.'
170
+ });
171
+ const finalized = yield this.retryBusy(() => {
172
+ this.assertSession(session);
173
+ return this.rest.post(`/v1/assets/${encodeURIComponent(prepared.id)}/finalize`);
174
+ });
175
+ this.assertSession(session);
176
+ return finalized.data;
177
+ });
178
+ }
179
+ /** Existing project uploads remain available when saved uploads cannot be used. */
180
+ tryBindFile(file, contentType, binding) {
181
+ return __awaiter(this, void 0, void 0, function* () {
182
+ const size = file instanceof Blob ? file.size : file.byteLength;
183
+ if (!contentType || !SUPPORTED_TYPES.has(contentType) || !size || size > 100 * 1024 * 1024)
184
+ return false;
185
+ const session = this.session;
186
+ if (!(yield this.canAutomaticallySave()))
187
+ return false;
188
+ this.assertSession(session);
189
+ try {
190
+ const saved = yield this.upload(file, contentType, file instanceof Blob && 'name' in file && typeof file.name === 'string'
191
+ ? file.name
192
+ : 'Saved upload');
193
+ this.assertSession(session);
194
+ yield this.bind(saved.id, binding);
195
+ return true;
196
+ }
197
+ catch (error) {
198
+ // Fallback is permitted only before a transfer was prepared. Checksum,
199
+ // storage-access and binding failures must surface before job submission.
200
+ if (error instanceof index_js_1.ApiError &&
201
+ this.preparationFailures.has(error) &&
202
+ [400, 403, 404, 409, 410, 503].includes(error.status))
203
+ return false;
204
+ throw error;
205
+ }
206
+ });
207
+ }
208
+ }
209
+ exports.default = ReusableUploads;
210
+ //# sourceMappingURL=ReusableUploads.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReusableUploads.js","sourceRoot":"","sources":["../../src/Projects/ReusableUploads.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import ApiGroup, { ApiConfig } from '../ApiGroup.js';
2
+ import ReusableUploads from './ReusableUploads.js';
2
3
  import { AvailableModel, EnhancementStrength, EstimateRequest, ImageUrlParams, MediaUrlParams, CostEstimation, ProjectParams, SizePreset, SupportedModel, VideoEstimateRequest, AudioEstimateRequest } from './types/index.js';
3
4
  import { type RecoveredProject } from '../ApiClient/WebSocketClient/events.js';
4
5
  import Project from './Project.js';
@@ -52,6 +53,9 @@ export type ProjectStatusSnapshot = Partial<Omit<RawProject, 'status' | 'workerJ
52
53
  completedWorkerJobs: RawProject['completedWorkerJobs'];
53
54
  };
54
55
  declare class ProjectsApi extends ApiGroup<ProjectApiEvents> {
56
+ private _assets?;
57
+ /** Manage subscriber uploads once and reuse them across projects. */
58
+ get assets(): ReusableUploads;
55
59
  private _availableModels;
56
60
  private _currentNetworkType;
57
61
  private projects;
@@ -13,6 +13,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  const ApiGroup_js_1 = __importDefault(require("../ApiGroup.js"));
16
+ const ReusableUploads_js_1 = __importDefault(require("./ReusableUploads.js"));
16
17
  const Project_js_1 = __importDefault(require("./Project.js"));
17
18
  const recovery_js_1 = require("./recovery.js");
18
19
  const createJobRequestMessage_js_1 = __importDefault(require("./createJobRequestMessage.js"));
@@ -250,6 +251,11 @@ const MISSING_PROJECT_RETRY_MS = 2500;
250
251
  const IN_FLIGHT_LOOKUP_STATUSES = new Set(['pending', 'queued', 'processing']);
251
252
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
252
253
  class ProjectsApi extends ApiGroup_js_1.default {
254
+ /** Manage subscriber uploads once and reuse them across projects. */
255
+ get assets() {
256
+ var _a;
257
+ return ((_a = this._assets) !== null && _a !== void 0 ? _a : (this._assets = new ReusableUploads_js_1.default(this.client.rest)));
258
+ }
253
259
  get availableModels() {
254
260
  return this._availableModels;
255
261
  }
@@ -1530,6 +1536,8 @@ class ProjectsApi extends ApiGroup_js_1.default {
1530
1536
  return __awaiter(this, void 0, void 0, function* () {
1531
1537
  const imageId = (0, getUUID_js_1.default)();
1532
1538
  const contentType = getFileContentType(file);
1539
+ if (yield this.assets.tryBindFile(file, contentType, { projectId, type: 'startingImage' }))
1540
+ return imageId;
1533
1541
  const presignedUrl = yield this.uploadUrl({
1534
1542
  imageId,
1535
1543
  jobId: projectId,
@@ -1558,6 +1566,8 @@ class ProjectsApi extends ApiGroup_js_1.default {
1558
1566
  return __awaiter(this, void 0, void 0, function* () {
1559
1567
  const imageId = (0, getUUID_js_1.default)();
1560
1568
  const contentType = getFileContentType(file);
1569
+ if (yield this.assets.tryBindFile(file, contentType, { projectId, type: 'cnImage' }))
1570
+ return imageId;
1561
1571
  const presignedUrl = yield this.uploadUrl({
1562
1572
  imageId,
1563
1573
  jobId: projectId,
@@ -1587,6 +1597,11 @@ class ProjectsApi extends ApiGroup_js_1.default {
1587
1597
  const imageId = (0, getUUID_js_1.default)();
1588
1598
  const imageIndex = (index + 1);
1589
1599
  const contentType = getFileContentType(file);
1600
+ if (yield this.assets.tryBindFile(file, contentType, {
1601
+ projectId,
1602
+ type: `contextImage${imageIndex}`
1603
+ }))
1604
+ return imageId;
1590
1605
  const presignedUrl = yield this.uploadUrl({
1591
1606
  imageId,
1592
1607
  jobId: projectId,
@@ -1623,6 +1638,8 @@ class ProjectsApi extends ApiGroup_js_1.default {
1623
1638
  return __awaiter(this, void 0, void 0, function* () {
1624
1639
  const imageId = (0, getUUID_js_1.default)();
1625
1640
  const contentType = getFileContentType(file);
1641
+ if (yield this.assets.tryBindFile(file, contentType, { projectId, type: 'referenceImage' }))
1642
+ return imageId;
1626
1643
  const presignedUrl = yield this.uploadUrl({
1627
1644
  imageId,
1628
1645
  jobId: projectId,
@@ -1655,6 +1672,8 @@ class ProjectsApi extends ApiGroup_js_1.default {
1655
1672
  return __awaiter(this, void 0, void 0, function* () {
1656
1673
  const imageId = (0, getUUID_js_1.default)();
1657
1674
  const contentType = getFileContentType(file);
1675
+ if (yield this.assets.tryBindFile(file, contentType, { projectId, type: 'referenceMask' }))
1676
+ return imageId;
1658
1677
  const presignedUrl = yield this.uploadUrl({
1659
1678
  imageId,
1660
1679
  jobId: projectId,
@@ -1687,6 +1706,8 @@ class ProjectsApi extends ApiGroup_js_1.default {
1687
1706
  return __awaiter(this, void 0, void 0, function* () {
1688
1707
  const imageId = (0, getUUID_js_1.default)();
1689
1708
  const contentType = getFileContentType(file);
1709
+ if (yield this.assets.tryBindFile(file, contentType, { projectId, type: 'referenceImageEnd' }))
1710
+ return imageId;
1690
1711
  const presignedUrl = yield this.uploadUrl({
1691
1712
  imageId,
1692
1713
  jobId: projectId,
@@ -1720,6 +1741,8 @@ class ProjectsApi extends ApiGroup_js_1.default {
1720
1741
  uploadReferenceAudio(projectId, file, id) {
1721
1742
  return __awaiter(this, void 0, void 0, function* () {
1722
1743
  const contentType = getFileContentType(file);
1744
+ if (yield this.assets.tryBindFile(file, contentType, { projectId, type: 'referenceAudio', id }))
1745
+ return;
1723
1746
  const presignedUrl = yield this.mediaUploadUrl({
1724
1747
  jobId: projectId,
1725
1748
  type: 'referenceAudio',
@@ -1752,6 +1775,8 @@ class ProjectsApi extends ApiGroup_js_1.default {
1752
1775
  uploadReferenceVideo(projectId, file, id) {
1753
1776
  return __awaiter(this, void 0, void 0, function* () {
1754
1777
  const contentType = getFileContentType(file);
1778
+ if (yield this.assets.tryBindFile(file, contentType, { projectId, type: 'referenceVideo', id }))
1779
+ return;
1755
1780
  const presignedUrl = yield this.mediaUploadUrl({
1756
1781
  jobId: projectId,
1757
1782
  type: 'referenceVideo',