@sogni-ai/sogni-client 4.2.0-alpha.2 → 4.2.0-alpha.21
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 +148 -0
- package/CLAUDE.md +25 -3
- package/README.md +411 -136
- package/dist/Account/index.d.ts +4 -2
- package/dist/Account/index.js +27 -23
- package/dist/Account/index.js.map +1 -1
- package/dist/Account/types.d.ts +7 -0
- package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/index.d.ts +3 -1
- package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/index.js +26 -2
- package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/index.js.map +1 -1
- package/dist/ApiClient/WebSocketClient/eventSubscriptions.d.ts +33 -0
- package/dist/ApiClient/WebSocketClient/eventSubscriptions.js +39 -0
- package/dist/ApiClient/WebSocketClient/eventSubscriptions.js.map +1 -0
- package/dist/ApiClient/WebSocketClient/events.d.ts +24 -7
- package/dist/ApiClient/WebSocketClient/index.d.ts +5 -1
- package/dist/ApiClient/WebSocketClient/index.js +24 -1
- package/dist/ApiClient/WebSocketClient/index.js.map +1 -1
- package/dist/ApiClient/WebSocketClient/messages.d.ts +2 -0
- package/dist/ApiClient/WebSocketClient/types.d.ts +2 -0
- package/dist/ApiClient/index.d.ts +6 -1
- package/dist/ApiClient/index.js +7 -3
- package/dist/ApiClient/index.js.map +1 -1
- package/dist/Chat/ChatTools.d.ts +5 -49
- package/dist/Chat/ChatTools.js +311 -88
- package/dist/Chat/ChatTools.js.map +1 -1
- package/dist/Chat/index.d.ts +11 -2
- package/dist/Chat/index.js +78 -4
- package/dist/Chat/index.js.map +1 -1
- package/dist/Chat/modelRouting.d.ts +100 -0
- package/dist/Chat/modelRouting.js +441 -0
- package/dist/Chat/modelRouting.js.map +1 -0
- package/dist/Chat/sogniHostedTools.generated.json +529 -0
- package/dist/Chat/tools.d.ts +9 -55
- package/dist/Chat/tools.js +72 -228
- package/dist/Chat/tools.js.map +1 -1
- package/dist/Chat/types.d.ts +91 -2
- package/dist/CreativeWorkflows/index.d.ts +23 -0
- package/dist/CreativeWorkflows/index.js +274 -0
- package/dist/CreativeWorkflows/index.js.map +1 -0
- package/dist/CreativeWorkflows/types.d.ts +106 -0
- package/dist/CreativeWorkflows/types.js +3 -0
- package/dist/CreativeWorkflows/types.js.map +1 -0
- package/dist/Projects/Job.d.ts +6 -0
- package/dist/Projects/Job.js +60 -5
- package/dist/Projects/Job.js.map +1 -1
- package/dist/Projects/Project.js +15 -3
- package/dist/Projects/Project.js.map +1 -1
- package/dist/Projects/createJobRequestMessage.js +140 -6
- package/dist/Projects/createJobRequestMessage.js.map +1 -1
- package/dist/Projects/index.d.ts +10 -1
- package/dist/Projects/index.js +197 -58
- package/dist/Projects/index.js.map +1 -1
- package/dist/Projects/types/ModelOptions.d.ts +3 -3
- package/dist/Projects/types/ModelOptions.js +12 -5
- package/dist/Projects/types/ModelOptions.js.map +1 -1
- package/dist/Projects/types/ModelTiersRaw.d.ts +7 -7
- package/dist/Projects/types/RawProject.d.ts +2 -0
- package/dist/Projects/types/events.d.ts +5 -4
- package/dist/Projects/types/index.d.ts +77 -7
- package/dist/Projects/types/index.js.map +1 -1
- package/dist/Projects/utils/index.d.ts +8 -1
- package/dist/Projects/utils/index.js +22 -8
- package/dist/Projects/utils/index.js.map +1 -1
- package/dist/index.d.ts +28 -3
- package/dist/index.js +19 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/RestClient.d.ts +4 -1
- package/dist/lib/RestClient.js +17 -9
- package/dist/lib/RestClient.js.map +1 -1
- package/dist/lib/mediaValidation.d.ts +16 -0
- package/dist/lib/mediaValidation.js +280 -0
- package/dist/lib/mediaValidation.js.map +1 -0
- package/dist/lib/validation.d.ts +6 -1
- package/dist/lib/validation.js +28 -2
- package/dist/lib/validation.js.map +1 -1
- package/llms-full.txt +372 -133
- package/llms.txt +197 -86
- package/package.json +13 -4
- package/src/Account/index.ts +22 -2
- package/src/Account/types.ts +7 -0
- package/src/ApiClient/WebSocketClient/BrowserWebSocketClient/index.ts +47 -3
- package/src/ApiClient/WebSocketClient/eventSubscriptions.ts +92 -0
- package/src/ApiClient/WebSocketClient/events.ts +25 -7
- package/src/ApiClient/WebSocketClient/index.ts +33 -1
- package/src/ApiClient/WebSocketClient/messages.ts +2 -0
- package/src/ApiClient/WebSocketClient/types.ts +2 -0
- package/src/ApiClient/index.ts +32 -2
- package/src/Chat/ChatTools.ts +395 -95
- package/src/Chat/index.ts +149 -5
- package/src/Chat/modelRouting.ts +602 -0
- package/src/Chat/sogniHostedTools.generated.json +529 -0
- package/src/Chat/tools.ts +98 -245
- package/src/Chat/types.ts +100 -2
- package/src/CreativeWorkflows/index.ts +290 -0
- package/src/CreativeWorkflows/types.ts +134 -0
- package/src/Projects/Job.ts +76 -5
- package/src/Projects/Project.ts +13 -3
- package/src/Projects/createJobRequestMessage.ts +152 -13
- package/src/Projects/index.ts +230 -52
- package/src/Projects/types/ModelOptions.ts +15 -8
- package/src/Projects/types/ModelTiersRaw.ts +7 -7
- package/src/Projects/types/RawProject.ts +2 -0
- package/src/Projects/types/events.ts +5 -4
- package/src/Projects/types/index.ts +86 -6
- package/src/Projects/utils/index.ts +24 -8
- package/src/index.ts +93 -0
- package/src/lib/RestClient.ts +15 -5
- package/src/lib/mediaValidation.ts +367 -0
- package/src/lib/validation.ts +38 -2
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
import { getVideoWorkflowType } from '../Projects/utils';
|
|
2
|
+
|
|
3
|
+
export { getVideoWorkflowType };
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Public SDK-local copy of the pure model routing helpers from
|
|
7
|
+
* @sogni/creative-agent/backbone/reference. Keep this file self-contained so
|
|
8
|
+
* published SDK consumers do not need the private creative-agent package.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export type BackboneMediaType = 'image' | 'video' | 'audio';
|
|
12
|
+
|
|
13
|
+
export type VideoWorkflow =
|
|
14
|
+
| 't2v'
|
|
15
|
+
| 'i2v'
|
|
16
|
+
| 's2v'
|
|
17
|
+
| 'ia2v'
|
|
18
|
+
| 'a2v'
|
|
19
|
+
| 'v2v'
|
|
20
|
+
| 'animate-move'
|
|
21
|
+
| 'animate-replace';
|
|
22
|
+
|
|
23
|
+
export type VideoControlMode =
|
|
24
|
+
| 'animate-move'
|
|
25
|
+
| 'animate-replace'
|
|
26
|
+
| 'seedance-v2v'
|
|
27
|
+
| 'canny'
|
|
28
|
+
| 'pose'
|
|
29
|
+
| 'depth'
|
|
30
|
+
| 'detailer';
|
|
31
|
+
|
|
32
|
+
export interface BackboneAvailableModel {
|
|
33
|
+
id: string;
|
|
34
|
+
media?: string;
|
|
35
|
+
workerCount?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface SelectBackboneModelInput {
|
|
39
|
+
mediaType: BackboneMediaType;
|
|
40
|
+
requestedModel?: string;
|
|
41
|
+
workflows?: VideoWorkflow[];
|
|
42
|
+
filter?: (modelId: string) => boolean;
|
|
43
|
+
preferredModelIds?: string[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface SelectedBackboneModel {
|
|
47
|
+
modelId: string;
|
|
48
|
+
model: BackboneAvailableModel;
|
|
49
|
+
selectedBy: 'requestedModel' | 'preferredModel' | 'workerCount';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface HostedToolSchemaProperty {
|
|
53
|
+
type?: string | string[];
|
|
54
|
+
enum?: unknown[];
|
|
55
|
+
items?: HostedToolSchemaProperty;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface HostedToolSchema {
|
|
59
|
+
required?: string[];
|
|
60
|
+
properties?: Record<string, HostedToolSchemaProperty>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface HostedToolDefinition {
|
|
64
|
+
function: {
|
|
65
|
+
name: string;
|
|
66
|
+
parameters?: HostedToolSchema;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface ValidateHostedToolArgumentsOptions {
|
|
71
|
+
skipEnumProperties?: string[];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const PREFERRED_MODEL_IDS = {
|
|
75
|
+
image: {
|
|
76
|
+
gptImage2: 'gpt-image-2',
|
|
77
|
+
flux1Schnell: 'flux1-schnell-fp8',
|
|
78
|
+
flux2: 'flux2_dev_fp8',
|
|
79
|
+
chromaFlash: 'chroma-v.46-flash_fp8',
|
|
80
|
+
zTurbo: 'z_image_turbo_bf16'
|
|
81
|
+
},
|
|
82
|
+
video: {
|
|
83
|
+
t2v: 'ltx23-22b-fp8_t2v_distilled',
|
|
84
|
+
i2v: 'ltx23-22b-fp8_i2v_distilled',
|
|
85
|
+
a2v: 'ltx23-22b-fp8_a2v_distilled',
|
|
86
|
+
ia2v: 'ltx23-22b-fp8_ia2v_distilled',
|
|
87
|
+
s2v: 'wan_v2.2-14b-fp8_s2v_lightx2v',
|
|
88
|
+
v2v: 'ltx23-22b-fp8_v2v_distilled',
|
|
89
|
+
seedanceT2v: 'seedance-2-0',
|
|
90
|
+
seedanceI2v: 'seedance-2-0',
|
|
91
|
+
seedanceIa2v: 'seedance-2-0',
|
|
92
|
+
seedanceFastT2v: 'seedance-2-0-fast',
|
|
93
|
+
seedanceFastI2v: 'seedance-2-0-fast',
|
|
94
|
+
seedanceV2v: 'seedance-2-0',
|
|
95
|
+
animateMove: 'wan_v2.2-14b-fp8_animate-move_lightx2v',
|
|
96
|
+
animateReplace: 'wan_v2.2-14b-fp8_animate-replace_lightx2v'
|
|
97
|
+
},
|
|
98
|
+
audio: {
|
|
99
|
+
aceStepTurbo: 'ace_step_1.5_turbo',
|
|
100
|
+
aceStepSft: 'ace_step_1.5_sft'
|
|
101
|
+
}
|
|
102
|
+
} as const;
|
|
103
|
+
|
|
104
|
+
const GPT_IMAGE_MODEL_ALIASES = [
|
|
105
|
+
'chatgpt',
|
|
106
|
+
'chatgpt-image',
|
|
107
|
+
'chat-gpt',
|
|
108
|
+
'chat-gpt-image',
|
|
109
|
+
'openai',
|
|
110
|
+
'openai-image',
|
|
111
|
+
'open-ai',
|
|
112
|
+
'open-ai-image',
|
|
113
|
+
'gpt',
|
|
114
|
+
'gpt-image',
|
|
115
|
+
'gpt2',
|
|
116
|
+
'gpt-2',
|
|
117
|
+
'gpt2-image',
|
|
118
|
+
'gpt-2-image',
|
|
119
|
+
'gptimage2',
|
|
120
|
+
'gpt-image2',
|
|
121
|
+
'gpt-image-2'
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
function normalizeSelectorKey(value: string): string {
|
|
125
|
+
return value
|
|
126
|
+
.trim()
|
|
127
|
+
.toLowerCase()
|
|
128
|
+
.replace(/[_\s]+/g, '-');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const IMAGE_MODEL_SELECTORS: Record<string, string> = {
|
|
132
|
+
...Object.fromEntries(
|
|
133
|
+
GPT_IMAGE_MODEL_ALIASES.map((alias) => [alias, PREFERRED_MODEL_IDS.image.gptImage2])
|
|
134
|
+
),
|
|
135
|
+
'z-turbo': 'z_image_turbo_bf16',
|
|
136
|
+
'z-image': 'z_image_bf16',
|
|
137
|
+
'chroma-v46-flash': 'chroma-v.46-flash_fp8',
|
|
138
|
+
'chroma-detail': 'chroma-v48-detail-svd_fp8',
|
|
139
|
+
'flux1-krea': 'flux1-krea-dev_fp8_scaled',
|
|
140
|
+
flux2: PREFERRED_MODEL_IDS.image.flux2,
|
|
141
|
+
'pony-v7': 'coreml-cyberrealisticPony_v7',
|
|
142
|
+
'qwen-2512': 'qwen_image_2512_fp8',
|
|
143
|
+
'qwen-2512-lightning': 'qwen_image_2512_fp8_lightning',
|
|
144
|
+
'albedo-xl': 'coreml-albedobaseXL_v31Large',
|
|
145
|
+
'animagine-xl': 'coreml-animagineXL40_v4Opt',
|
|
146
|
+
'anima-pencil-xl': 'coreml-animaPencilXL_v500',
|
|
147
|
+
'art-universe-xl': 'coreml-artUniverse_sdxlV60',
|
|
148
|
+
'hyphoria-real': 'coreml-hyphoriaRealIllu_v05',
|
|
149
|
+
'analog-madness-xl': 'coreml-analogMadnessSDXL_xl2',
|
|
150
|
+
'cyberrealistic-xl': 'coreml-cyberrealisticXL_v60',
|
|
151
|
+
'real-dream-xl': 'coreml-realDream_sdxlPony11',
|
|
152
|
+
'faetastic-xl': 'coreml-sdxlFaetastic_v24',
|
|
153
|
+
'zavychroma-xl': 'coreml-zavychromaxl_v80',
|
|
154
|
+
'pony-faetality': 'coreml-ponyFaetality_v11',
|
|
155
|
+
'dreamshaper-xl': 'coreml-DreamShaper-XL1-Alpha2'
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const EDIT_IMAGE_MODEL_SELECTORS: Record<string, string> = {
|
|
159
|
+
...Object.fromEntries(
|
|
160
|
+
GPT_IMAGE_MODEL_ALIASES.map((alias) => [alias, PREFERRED_MODEL_IDS.image.gptImage2])
|
|
161
|
+
),
|
|
162
|
+
'qwen-lightning': 'qwen_image_edit_2511_fp8_lightning',
|
|
163
|
+
qwen: 'qwen_image_edit_2511_fp8',
|
|
164
|
+
flux2: PREFERRED_MODEL_IDS.image.flux2
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const TEXT_VIDEO_MODEL_SELECTORS: Record<string, string> = {
|
|
168
|
+
ltx23: PREFERRED_MODEL_IDS.video.t2v,
|
|
169
|
+
wan22: 'wan_v2.2-14b-fp8_t2v_lightx2v',
|
|
170
|
+
seedance2: PREFERRED_MODEL_IDS.video.seedanceT2v,
|
|
171
|
+
'seedance2-fast': PREFERRED_MODEL_IDS.video.seedanceFastT2v
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const IMAGE_VIDEO_MODEL_SELECTORS: Record<string, string> = {
|
|
175
|
+
ltx23: PREFERRED_MODEL_IDS.video.i2v,
|
|
176
|
+
wan22: 'wan_v2.2-14b-fp8_i2v_lightx2v',
|
|
177
|
+
seedance2: PREFERRED_MODEL_IDS.video.seedanceI2v,
|
|
178
|
+
'seedance2-fast': PREFERRED_MODEL_IDS.video.seedanceFastI2v
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const VIDEO_TO_VIDEO_MODEL_SELECTORS: Record<string, string> = {
|
|
182
|
+
ltx23: PREFERRED_MODEL_IDS.video.v2v,
|
|
183
|
+
'ltx23-v2v': PREFERRED_MODEL_IDS.video.v2v,
|
|
184
|
+
seedance2: PREFERRED_MODEL_IDS.video.seedanceV2v
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const SOUND_TO_VIDEO_MODEL_SELECTORS: Record<string, string> = {
|
|
188
|
+
'wan-s2v': PREFERRED_MODEL_IDS.video.s2v,
|
|
189
|
+
seedance2: PREFERRED_MODEL_IDS.video.seedanceIa2v,
|
|
190
|
+
'ltx23-ia2v': PREFERRED_MODEL_IDS.video.ia2v,
|
|
191
|
+
'ltx23-a2v': PREFERRED_MODEL_IDS.video.a2v
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const MUSIC_MODEL_SELECTORS: Record<string, string> = {
|
|
195
|
+
turbo: PREFERRED_MODEL_IDS.audio.aceStepTurbo,
|
|
196
|
+
sft: PREFERRED_MODEL_IDS.audio.aceStepSft
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
export function clampVariationCount(value: unknown, fallback = 1): number {
|
|
200
|
+
const count = typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
201
|
+
return Math.max(1, Math.min(16, Math.round(count)));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function isNonEmptyString(value: unknown): value is string {
|
|
205
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function asStringArray(value: unknown): string[] {
|
|
209
|
+
if (!Array.isArray(value)) {
|
|
210
|
+
return [];
|
|
211
|
+
}
|
|
212
|
+
return value.filter(isNonEmptyString);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function asFiniteNumber(value: unknown): number | undefined {
|
|
216
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function asBooleanValue(value: unknown): boolean | undefined {
|
|
220
|
+
return typeof value === 'boolean' ? value : undefined;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function normalizeTimeSignature(value: unknown): string | undefined {
|
|
224
|
+
if (typeof value === 'string' && value.length > 0) {
|
|
225
|
+
return value;
|
|
226
|
+
}
|
|
227
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
228
|
+
return String(Math.round(value));
|
|
229
|
+
}
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function normalizeVideoControlMode(value: unknown): VideoControlMode {
|
|
234
|
+
switch (value) {
|
|
235
|
+
case 'animate-replace':
|
|
236
|
+
case 'seedance-v2v':
|
|
237
|
+
case 'canny':
|
|
238
|
+
case 'pose':
|
|
239
|
+
case 'depth':
|
|
240
|
+
case 'detailer':
|
|
241
|
+
return value;
|
|
242
|
+
default:
|
|
243
|
+
return 'animate-move';
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function getHostedVariationCount(
|
|
248
|
+
args: Record<string, unknown>,
|
|
249
|
+
fallback: unknown = 1
|
|
250
|
+
): number {
|
|
251
|
+
if (args.number_of_variations !== undefined) {
|
|
252
|
+
return clampVariationCount(args.number_of_variations);
|
|
253
|
+
}
|
|
254
|
+
return clampVariationCount(fallback, 1);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function resolveHostedToolModelSelector(
|
|
258
|
+
toolName: string,
|
|
259
|
+
args: Record<string, unknown>
|
|
260
|
+
): string | undefined {
|
|
261
|
+
if (!args || typeof args !== 'object' || Array.isArray(args)) {
|
|
262
|
+
return undefined;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const requestedModel = isNonEmptyString(args.model) ? args.model : undefined;
|
|
266
|
+
if (!requestedModel) {
|
|
267
|
+
return undefined;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let selectors: Record<string, string> | null = null;
|
|
271
|
+
switch (toolName) {
|
|
272
|
+
case 'sogni_generate_image':
|
|
273
|
+
selectors = IMAGE_MODEL_SELECTORS;
|
|
274
|
+
break;
|
|
275
|
+
case 'sogni_edit_image':
|
|
276
|
+
selectors = EDIT_IMAGE_MODEL_SELECTORS;
|
|
277
|
+
break;
|
|
278
|
+
case 'sogni_generate_video':
|
|
279
|
+
selectors =
|
|
280
|
+
isNonEmptyString(args.reference_image_url) || isNonEmptyString(args.reference_image_end_url)
|
|
281
|
+
? IMAGE_VIDEO_MODEL_SELECTORS
|
|
282
|
+
: TEXT_VIDEO_MODEL_SELECTORS;
|
|
283
|
+
break;
|
|
284
|
+
case 'sogni_sound_to_video':
|
|
285
|
+
selectors = SOUND_TO_VIDEO_MODEL_SELECTORS;
|
|
286
|
+
break;
|
|
287
|
+
case 'sogni_video_to_video':
|
|
288
|
+
selectors = VIDEO_TO_VIDEO_MODEL_SELECTORS;
|
|
289
|
+
break;
|
|
290
|
+
case 'sogni_generate_music':
|
|
291
|
+
selectors = MUSIC_MODEL_SELECTORS;
|
|
292
|
+
break;
|
|
293
|
+
default:
|
|
294
|
+
return requestedModel;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return (
|
|
298
|
+
selectors[requestedModel] ?? selectors[normalizeSelectorKey(requestedModel)] ?? requestedModel
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function matchesType(value: unknown, type: string): boolean {
|
|
303
|
+
switch (type) {
|
|
304
|
+
case 'array':
|
|
305
|
+
return Array.isArray(value);
|
|
306
|
+
case 'integer':
|
|
307
|
+
return typeof value === 'number' && Number.isInteger(value);
|
|
308
|
+
case 'number':
|
|
309
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
310
|
+
case 'boolean':
|
|
311
|
+
return typeof value === 'boolean';
|
|
312
|
+
case 'object':
|
|
313
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
314
|
+
case 'string':
|
|
315
|
+
return typeof value === 'string';
|
|
316
|
+
case 'null':
|
|
317
|
+
return value === null;
|
|
318
|
+
default:
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function typeLabel(type: string | string[] | undefined): string {
|
|
324
|
+
if (Array.isArray(type)) return type.join(' or ');
|
|
325
|
+
return type ?? 'valid value';
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function typeList(type: string | string[] | undefined): string[] {
|
|
329
|
+
if (!type) return [];
|
|
330
|
+
return Array.isArray(type) ? type : [type];
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function formatEnum(values: unknown[]): string {
|
|
334
|
+
return values.map((value) => JSON.stringify(value)).join(', ');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export function validateHostedToolArguments(
|
|
338
|
+
tools: HostedToolDefinition[],
|
|
339
|
+
toolName: string,
|
|
340
|
+
args: Record<string, unknown>,
|
|
341
|
+
options: ValidateHostedToolArgumentsOptions = {}
|
|
342
|
+
): { ok: boolean; errors: string[] } {
|
|
343
|
+
if (!args || typeof args !== 'object' || Array.isArray(args)) {
|
|
344
|
+
return {
|
|
345
|
+
ok: false,
|
|
346
|
+
errors: ['Tool arguments must be a JSON object']
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const tool = tools.find((candidate) => candidate.function.name === toolName);
|
|
351
|
+
if (!tool) {
|
|
352
|
+
return {
|
|
353
|
+
ok: false,
|
|
354
|
+
errors: [`Unknown hosted Sogni tool "${toolName}"`]
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const schema = tool.function.parameters;
|
|
359
|
+
if (!schema) {
|
|
360
|
+
return { ok: true, errors: [] };
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const errors: string[] = [];
|
|
364
|
+
const properties = schema.properties ?? {};
|
|
365
|
+
const skipEnumProperties = new Set(options.skipEnumProperties ?? ['model']);
|
|
366
|
+
|
|
367
|
+
for (const required of schema.required ?? []) {
|
|
368
|
+
if (args[required] === undefined || args[required] === null) {
|
|
369
|
+
errors.push(`Missing required argument "${required}"`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
for (const [name, value] of Object.entries(args)) {
|
|
374
|
+
if (value === undefined || value === null) continue;
|
|
375
|
+
|
|
376
|
+
const property = properties[name];
|
|
377
|
+
if (!property) continue;
|
|
378
|
+
|
|
379
|
+
const allowedTypes = typeList(property.type);
|
|
380
|
+
if (allowedTypes.length > 0 && !allowedTypes.some((type) => matchesType(value, type))) {
|
|
381
|
+
errors.push(`Argument "${name}" must be ${typeLabel(property.type)}`);
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (
|
|
386
|
+
property.enum &&
|
|
387
|
+
!skipEnumProperties.has(name) &&
|
|
388
|
+
!property.enum.some((candidate) => candidate === value)
|
|
389
|
+
) {
|
|
390
|
+
errors.push(`Argument "${name}" must be one of ${formatEnum(property.enum)}`);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (Array.isArray(value) && property.items?.type) {
|
|
394
|
+
const itemTypes = typeList(property.items.type);
|
|
395
|
+
value.forEach((item, index) => {
|
|
396
|
+
if (!itemTypes.some((type) => matchesType(item, type))) {
|
|
397
|
+
errors.push(`Argument "${name}[${index}]" must be ${typeLabel(property.items?.type)}`);
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return {
|
|
404
|
+
ok: errors.length === 0,
|
|
405
|
+
errors
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export function assertHostedToolArguments(
|
|
410
|
+
tools: HostedToolDefinition[],
|
|
411
|
+
toolName: string,
|
|
412
|
+
args: Record<string, unknown>,
|
|
413
|
+
options?: ValidateHostedToolArgumentsOptions
|
|
414
|
+
): void {
|
|
415
|
+
const result = validateHostedToolArguments(tools, toolName, args, options);
|
|
416
|
+
if (!result.ok) {
|
|
417
|
+
throw new Error(`Invalid ${toolName} arguments: ${result.errors.join('; ')}`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
422
|
+
return typeof value === 'object' && value !== null;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function nonEmptyString(value: unknown): string | null {
|
|
426
|
+
return typeof value === 'string' && value.trim().length > 0 ? value : null;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function safeJsonStringify(value: unknown): string | null {
|
|
430
|
+
const seen = new WeakSet<object>();
|
|
431
|
+
try {
|
|
432
|
+
return JSON.stringify(value, (_key, nestedValue: unknown) => {
|
|
433
|
+
if (typeof nestedValue === 'function') {
|
|
434
|
+
return `[Function ${(nestedValue as { name?: string }).name || 'anonymous'}]`;
|
|
435
|
+
}
|
|
436
|
+
if (isObject(nestedValue)) {
|
|
437
|
+
if (seen.has(nestedValue)) {
|
|
438
|
+
return '[Circular]';
|
|
439
|
+
}
|
|
440
|
+
seen.add(nestedValue);
|
|
441
|
+
}
|
|
442
|
+
return nestedValue;
|
|
443
|
+
});
|
|
444
|
+
} catch {
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export function serializeUnknownError(error: unknown, fallback = 'Unknown error'): string {
|
|
450
|
+
if (error instanceof Error) {
|
|
451
|
+
return nonEmptyString(error.message) ?? error.name ?? fallback;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const directString = nonEmptyString(error);
|
|
455
|
+
if (directString) {
|
|
456
|
+
return directString;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (!isObject(error)) {
|
|
460
|
+
return String(error ?? fallback);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
for (const key of ['message', 'errorMessage', 'reason', 'description']) {
|
|
464
|
+
const message = nonEmptyString(error[key]);
|
|
465
|
+
if (message) return message;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const nestedError = error.error;
|
|
469
|
+
if (nestedError !== undefined && nestedError !== error) {
|
|
470
|
+
const nestedMessage = serializeUnknownError(nestedError, '');
|
|
471
|
+
if (nestedMessage) return nestedMessage;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const nestedCause = error.cause;
|
|
475
|
+
if (nestedCause !== undefined && nestedCause !== error) {
|
|
476
|
+
const nestedMessage = serializeUnknownError(nestedCause, '');
|
|
477
|
+
if (nestedMessage) return nestedMessage;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return safeJsonStringify(error) ?? fallback;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
export function isEditImageModel(modelId: string): boolean {
|
|
484
|
+
return (
|
|
485
|
+
modelId === PREFERRED_MODEL_IDS.image.gptImage2 ||
|
|
486
|
+
modelId.startsWith('qwen_image_edit_') ||
|
|
487
|
+
modelId.startsWith('flux2_') ||
|
|
488
|
+
modelId.includes('kontext')
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const SEEDANCE_CANONICAL_WORKFLOWS: VideoWorkflow[] = ['t2v', 'i2v', 'ia2v', 'v2v'];
|
|
493
|
+
|
|
494
|
+
function getCompatibleVideoWorkflows(modelId: string): VideoWorkflow[] {
|
|
495
|
+
if (modelId === 'seedance-2-0' || modelId === 'seedance-2-0-fast') {
|
|
496
|
+
return SEEDANCE_CANONICAL_WORKFLOWS;
|
|
497
|
+
}
|
|
498
|
+
const workflow = getVideoWorkflowType(modelId);
|
|
499
|
+
return workflow ? [workflow] : [];
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
export function filterVideoModelsByWorkflow(
|
|
503
|
+
availableModels: Array<{ id: string; media?: string }>,
|
|
504
|
+
workflows: VideoWorkflow[]
|
|
505
|
+
): string[] {
|
|
506
|
+
return availableModels
|
|
507
|
+
.filter((model) => model.media === 'video')
|
|
508
|
+
.filter((model) => {
|
|
509
|
+
const compatibleWorkflows = getCompatibleVideoWorkflows(model.id);
|
|
510
|
+
return compatibleWorkflows.some((workflow) => workflows.includes(workflow));
|
|
511
|
+
})
|
|
512
|
+
.map((model) => model.id);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
export function getVideoDefaults(modelId: string): { width: number; height: number; fps: number } {
|
|
516
|
+
const workflow = getVideoWorkflowType(modelId);
|
|
517
|
+
const isLtx2 = modelId.startsWith('ltx2-') || modelId.startsWith('ltx23-');
|
|
518
|
+
const isSeedance = modelId.startsWith('seedance-2-0');
|
|
519
|
+
|
|
520
|
+
if (workflow === 's2v' || workflow === 'animate-move' || workflow === 'animate-replace') {
|
|
521
|
+
return { width: 832, height: 480, fps: 16 };
|
|
522
|
+
}
|
|
523
|
+
if (modelId.includes('seedance-2-0-fast')) {
|
|
524
|
+
return { width: 1280, height: 720, fps: 24 };
|
|
525
|
+
}
|
|
526
|
+
if (isSeedance) {
|
|
527
|
+
return { width: 1920, height: 1088, fps: 24 };
|
|
528
|
+
}
|
|
529
|
+
if (isLtx2) {
|
|
530
|
+
return { width: 1920, height: 1088, fps: 24 };
|
|
531
|
+
}
|
|
532
|
+
return { width: 848, height: 480, fps: 16 };
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function workerCount(model: BackboneAvailableModel): number {
|
|
536
|
+
return typeof model.workerCount === 'number' && Number.isFinite(model.workerCount)
|
|
537
|
+
? model.workerCount
|
|
538
|
+
: 0;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
export function selectBackboneModel(
|
|
542
|
+
models: BackboneAvailableModel[],
|
|
543
|
+
options: SelectBackboneModelInput
|
|
544
|
+
): SelectedBackboneModel {
|
|
545
|
+
const byMedia = models.filter((model) => model.media === options.mediaType);
|
|
546
|
+
if (byMedia.length === 0) {
|
|
547
|
+
throw new Error(`No ${options.mediaType} models currently available on the network`);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const compatible = byMedia.filter((model) => {
|
|
551
|
+
if (options.filter && !options.filter(model.id)) {
|
|
552
|
+
return false;
|
|
553
|
+
}
|
|
554
|
+
if (options.workflows) {
|
|
555
|
+
const compatibleWorkflows = getCompatibleVideoWorkflows(model.id);
|
|
556
|
+
return compatibleWorkflows.some((workflow) => options.workflows?.includes(workflow));
|
|
557
|
+
}
|
|
558
|
+
return true;
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
if (options.requestedModel) {
|
|
562
|
+
const requested = compatible.find((model) => model.id === options.requestedModel);
|
|
563
|
+
if (requested) {
|
|
564
|
+
return {
|
|
565
|
+
modelId: requested.id,
|
|
566
|
+
model: requested,
|
|
567
|
+
selectedBy: 'requestedModel'
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (compatible.length === 0) {
|
|
573
|
+
if (options.workflows) {
|
|
574
|
+
throw new Error(
|
|
575
|
+
`No compatible ${options.mediaType} models available for workflows: ${options.workflows.join(', ')}`
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
throw new Error(`No compatible ${options.mediaType} models currently available on the network`);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
if (options.preferredModelIds) {
|
|
582
|
+
for (const preferredId of options.preferredModelIds) {
|
|
583
|
+
const preferred = compatible
|
|
584
|
+
.filter((model) => model.id === preferredId)
|
|
585
|
+
.sort((a, b) => workerCount(b) - workerCount(a))[0];
|
|
586
|
+
if (preferred) {
|
|
587
|
+
return {
|
|
588
|
+
modelId: preferred.id,
|
|
589
|
+
model: preferred,
|
|
590
|
+
selectedBy: 'preferredModel'
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const selected = [...compatible].sort((a, b) => workerCount(b) - workerCount(a))[0];
|
|
597
|
+
return {
|
|
598
|
+
modelId: selected.id,
|
|
599
|
+
model: selected,
|
|
600
|
+
selectedBy: 'workerCount'
|
|
601
|
+
};
|
|
602
|
+
}
|