@sogni-ai/sogni-client 5.60.2 → 5.60.4
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/CHANGELOG.md +15 -0
- package/dist/ApiClient/WebSocketClient/events.d.ts +6 -0
- package/dist/Projects/Job.d.ts +1 -1
- package/dist/Projects/Job.js.map +1 -1
- package/dist/Projects/Project.d.ts +5 -4
- package/dist/Projects/Project.js +41 -24
- package/dist/Projects/Project.js.map +1 -1
- package/dist/Projects/index.d.ts +2 -1
- package/dist/Projects/index.js +19 -6
- package/dist/Projects/index.js.map +1 -1
- package/dist/types/ErrorData.d.ts +8 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist-esm/Projects/Job.js.map +1 -1
- package/dist-esm/Projects/Project.js +41 -24
- package/dist-esm/Projects/Project.js.map +1 -1
- package/dist-esm/Projects/index.js +19 -6
- package/dist-esm/Projects/index.js.map +1 -1
- package/dist-esm/version.js +1 -1
- package/package.json +1 -1
- package/src/ApiClient/WebSocketClient/events.ts +7 -0
- package/src/Projects/Job.ts +5 -1
- package/src/Projects/Project.ts +77 -54
- package/src/Projects/index.ts +21 -8
- package/src/types/ErrorData.ts +8 -8
- package/src/version.ts +1 -1
package/src/Projects/Project.ts
CHANGED
|
@@ -502,68 +502,83 @@ class Project extends DataEntity<ProjectData, ProjectEventMap> {
|
|
|
502
502
|
* window, which is why a bare 404 must never be read as "lost".
|
|
503
503
|
*
|
|
504
504
|
* - socket says alive -> waiting is normal, keep the project alive.
|
|
505
|
-
* -
|
|
506
|
-
*
|
|
507
|
-
*
|
|
505
|
+
* - recovery confirms absence from the live list and both REST lookups ->
|
|
506
|
+
* genuinely lost, count a strike.
|
|
507
|
+
* - inconclusive recovery -> keep waiting without canceling the generation.
|
|
508
508
|
*/
|
|
509
509
|
private async _runStalenessCheck() {
|
|
510
510
|
const liveProjectIds = await this._api._listActiveProjectIds();
|
|
511
|
+
if (this.finished) return;
|
|
511
512
|
if (liveProjectIds?.includes(this.id)) {
|
|
512
513
|
// A queued project emits no events by design; this is not staleness.
|
|
513
514
|
this._failedSyncAttempts = 0;
|
|
514
515
|
this._keepAlive();
|
|
515
516
|
return;
|
|
516
517
|
}
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
.
|
|
518
|
+
const queueRevision = this._queueRevision;
|
|
519
|
+
try {
|
|
520
|
+
const resolved = await this._api.resolveMissing([this.id], { attempts: 1, delayMs: 0 });
|
|
521
|
+
if (this.finished) return; // a live event may have settled it during recovery
|
|
522
|
+
const resolution = resolved[this.id];
|
|
523
|
+
if (resolution?.state === 'finished' || resolution?.state === 'terminal') {
|
|
524
|
+
const data =
|
|
525
|
+
resolution.state === 'finished'
|
|
526
|
+
? resolution.project
|
|
527
|
+
: {
|
|
528
|
+
...resolution.project,
|
|
529
|
+
status:
|
|
530
|
+
resolution.project.status === 'failed'
|
|
531
|
+
? ('errored' as const)
|
|
532
|
+
: ('cancelled' as const)
|
|
533
|
+
};
|
|
534
|
+
await this._syncToServer(data, queueRevision);
|
|
535
|
+
}
|
|
536
|
+
if (resolution?.state !== 'lost') {
|
|
521
537
|
this._failedSyncAttempts = 0;
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
this._logger.error(
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
)
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
error: { code: 0, message: 'Project timed out. Please try again or contact support.' }
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
});
|
|
538
|
+
this._keepAlive();
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
this._failedSyncAttempts++;
|
|
542
|
+
if (this._failedSyncAttempts >= MAX_FAILED_SYNC_ATTEMPTS) {
|
|
543
|
+
this._logger.error(
|
|
544
|
+
`Failed to sync project data after ${MAX_FAILED_SYNC_ATTEMPTS} attempts. Stopping further attempts.`
|
|
545
|
+
);
|
|
546
|
+
this._api._notifyProjectTimedOut(this.id).catch((cancelError) => {
|
|
547
|
+
this._logger.error(`Failed to notify socket server that project ${this.id} timed out`);
|
|
548
|
+
this._logger.error(cancelError);
|
|
549
|
+
});
|
|
550
|
+
clearInterval(this._timeout!);
|
|
551
|
+
this._timeout = null;
|
|
552
|
+
this.jobs.forEach((job) => {
|
|
553
|
+
if (!job.finished) {
|
|
554
|
+
job._update({
|
|
555
|
+
status: 'failed',
|
|
556
|
+
error: { code: 0, message: 'Job timed out' }
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
this._update({
|
|
561
|
+
status: 'failed',
|
|
562
|
+
error: { code: 0, message: 'Project timed out. Please try again or contact support.' }
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
} catch (error) {
|
|
566
|
+
// A failed request is not evidence that the server lost the generation.
|
|
567
|
+
this._failedSyncAttempts = 0;
|
|
568
|
+
this._logger.error(error);
|
|
569
|
+
}
|
|
558
570
|
}
|
|
559
571
|
|
|
560
572
|
/**
|
|
561
573
|
* Sync project data with the data received from the REST API.
|
|
562
574
|
* @internal
|
|
563
575
|
*/
|
|
564
|
-
async _syncToServer(
|
|
565
|
-
|
|
566
|
-
|
|
576
|
+
async _syncToServer(
|
|
577
|
+
snapshot?: Partial<RawProject> & Pick<RawProject, 'status' | 'completedWorkerJobs'>,
|
|
578
|
+
queueRevision = this._queueRevision
|
|
579
|
+
) {
|
|
580
|
+
const wasFinished = this.finished;
|
|
581
|
+
const data = snapshot ?? (await this._api.get(this.id));
|
|
567
582
|
this._setQueueState(data.waitingReason, data.jobWaitingReasons, queueRevision);
|
|
568
583
|
const jobData = data.completedWorkerJobs.reduce((acc: Record<string, RawJob>, job) => {
|
|
569
584
|
const jobId = job.imgID || getUUID();
|
|
@@ -589,11 +604,18 @@ class Project extends DataEntity<ProjectData, ProjectEventMap> {
|
|
|
589
604
|
// If there are any jobs left in jobData, it means they are new jobs that are not in the project yet
|
|
590
605
|
if (Object.keys(jobData).length) {
|
|
591
606
|
for (const job of Object.values(jobData)) {
|
|
592
|
-
const jobInstance = Job.fromRaw(
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
607
|
+
const jobInstance = Job.fromRaw(
|
|
608
|
+
{
|
|
609
|
+
id: this.id,
|
|
610
|
+
stepCount: data.stepCount ?? this.params.steps ?? 0
|
|
611
|
+
},
|
|
612
|
+
job,
|
|
613
|
+
{
|
|
614
|
+
api: this._api,
|
|
615
|
+
logger: this._logger,
|
|
616
|
+
project: this
|
|
617
|
+
}
|
|
618
|
+
);
|
|
597
619
|
this._addJob(jobInstance);
|
|
598
620
|
// A job discovered for the first time through the REST snapshot needs
|
|
599
621
|
// the same sync a tracked job gets. `Job.fromRaw` only copies the
|
|
@@ -611,14 +633,15 @@ class Project extends DataEntity<ProjectData, ProjectEventMap> {
|
|
|
611
633
|
}
|
|
612
634
|
}
|
|
613
635
|
|
|
636
|
+
if (!wasFinished && this.finished) return;
|
|
614
637
|
const delta: Partial<ProjectData> = {
|
|
615
638
|
params: {
|
|
616
639
|
...this.data.params,
|
|
617
|
-
numberOfMedia: data.imageCount,
|
|
618
|
-
steps: data.stepCount
|
|
640
|
+
...(data.imageCount !== undefined ? { numberOfMedia: data.imageCount } : {}),
|
|
641
|
+
...(data.stepCount !== undefined ? { steps: data.stepCount } : {})
|
|
619
642
|
}
|
|
620
643
|
};
|
|
621
|
-
if (delta.params && isImageParams(delta.params)) {
|
|
644
|
+
if (delta.params && isImageParams(delta.params) && data.previewCount !== undefined) {
|
|
622
645
|
delta.params.numberOfPreviews = data.previewCount;
|
|
623
646
|
}
|
|
624
647
|
if (PROJECT_STATUS_MAP[data.status]) {
|
package/src/Projects/index.ts
CHANGED
|
@@ -906,6 +906,9 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
906
906
|
this.emit('project', { type: 'completed', projectId: data.jobID });
|
|
907
907
|
return;
|
|
908
908
|
case 'initiatingModel':
|
|
909
|
+
// LLM startup notifications share this channel but have no render id.
|
|
910
|
+
// ChatApi handles those; only media frames can produce a JobEvent.
|
|
911
|
+
if (typeof data.imgID !== 'string') return;
|
|
909
912
|
this.emit('job', {
|
|
910
913
|
type: 'initiating',
|
|
911
914
|
projectId: data.jobID,
|
|
@@ -918,6 +921,7 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
918
921
|
});
|
|
919
922
|
return;
|
|
920
923
|
case 'jobStarted': {
|
|
924
|
+
if (typeof data.imgID !== 'string') return;
|
|
921
925
|
this.emit('job', {
|
|
922
926
|
type: 'started',
|
|
923
927
|
projectId: data.jobID,
|
|
@@ -1753,9 +1757,8 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
1753
1757
|
// Before failing anything, ask the owner-scoped live lookup. It can
|
|
1754
1758
|
// confirm an active project or a terminal failure/cancellation without
|
|
1755
1759
|
// a full record. A successful completion still needs its result record
|
|
1756
|
-
// and stays unverified until that arrives.
|
|
1757
|
-
//
|
|
1758
|
-
// `lost` verdict.
|
|
1760
|
+
// and stays unverified until that arrives. Failed lookups are inconclusive;
|
|
1761
|
+
// only a 404 together with an available live list can confirm absence.
|
|
1759
1762
|
const unlisted = pending.filter((id) => !live?.includes(id));
|
|
1760
1763
|
const checks = new Map(
|
|
1761
1764
|
await Promise.all(
|
|
@@ -1773,6 +1776,13 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
1773
1776
|
result[id] = check;
|
|
1774
1777
|
continue;
|
|
1775
1778
|
}
|
|
1779
|
+
if (live === null) {
|
|
1780
|
+
result[id] = {
|
|
1781
|
+
state: 'unknown',
|
|
1782
|
+
error: new Error('The live project list is not available')
|
|
1783
|
+
};
|
|
1784
|
+
continue;
|
|
1785
|
+
}
|
|
1776
1786
|
// Nothing on the server knows it. A request that died with a dropped
|
|
1777
1787
|
// connection is sent again; one just (re)sent is still being admitted.
|
|
1778
1788
|
result[id] =
|
|
@@ -1842,7 +1852,8 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
1842
1852
|
|
|
1843
1853
|
/**
|
|
1844
1854
|
* Second opinion for a project neither the terminal REST record nor the live
|
|
1845
|
-
* socket list knows. Returns `undefined`
|
|
1855
|
+
* socket list knows. Returns `undefined` for a 404; other lookup failures
|
|
1856
|
+
* remain unknown, preserving the error and any retry-after information.
|
|
1846
1857
|
*/
|
|
1847
1858
|
private async _lookupUnlistedProject(projectId: string): Promise<ProjectResolution | undefined> {
|
|
1848
1859
|
try {
|
|
@@ -1864,8 +1875,8 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
1864
1875
|
};
|
|
1865
1876
|
}
|
|
1866
1877
|
return undefined;
|
|
1867
|
-
} catch {
|
|
1868
|
-
return undefined;
|
|
1878
|
+
} catch (error: any) {
|
|
1879
|
+
return error?.status === 404 ? undefined : { state: 'unknown', error };
|
|
1869
1880
|
}
|
|
1870
1881
|
}
|
|
1871
1882
|
|
|
@@ -2744,7 +2755,8 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
2744
2755
|
};
|
|
2745
2756
|
projects.set(projectId, project);
|
|
2746
2757
|
}
|
|
2747
|
-
const finishedAt =
|
|
2758
|
+
const finishedAt =
|
|
2759
|
+
typeof job.endTime === 'number' && job.endTime > 0 ? job.endTime : undefined;
|
|
2748
2760
|
project.jobs.push({
|
|
2749
2761
|
id: job.imgID || job.id,
|
|
2750
2762
|
status: resultJobStatus(job),
|
|
@@ -3449,7 +3461,8 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
3449
3461
|
);
|
|
3450
3462
|
}
|
|
3451
3463
|
const keyframeCount =
|
|
3452
|
-
params.keyframeCount ??
|
|
3464
|
+
params.keyframeCount ??
|
|
3465
|
+
(Array.isArray(params.keyframes) ? params.keyframes.length : undefined);
|
|
3453
3466
|
if (Number.isFinite(keyframeCount) && (keyframeCount as number) > 0) {
|
|
3454
3467
|
query.set('keyframeCount', String(Math.floor(keyframeCount as number)));
|
|
3455
3468
|
}
|
package/src/types/ErrorData.ts
CHANGED
|
@@ -4,14 +4,14 @@ interface ErrorData {
|
|
|
4
4
|
code: number;
|
|
5
5
|
originalCode?: string;
|
|
6
6
|
message: string;
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Coarse server-reported cause of an external generation failure. Known
|
|
9
|
+
* values include `content_policy`, `input_validation`, `timeout`,
|
|
10
|
+
* `result_storage`, `cancelled`, `vendor_failed`, `asset_resolution`, and
|
|
11
|
+
* `vendor_transient`. Servers may add categories; handle unknown strings
|
|
12
|
+
* with a generic failure message.
|
|
13
|
+
*/
|
|
14
|
+
vendorFailureCategory?: string;
|
|
15
15
|
/**
|
|
16
16
|
* Discriminator set to `true` when this render-path error is a subscription
|
|
17
17
|
* FEATURE-gate denial (socket error code 4081). The four fields below are
|
package/src/version.ts
CHANGED