@sogni-ai/sogni-client 4.2.0-alpha.21 → 4.2.0-alpha.23
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 +14 -0
- package/CLAUDE.md +7 -0
- package/README.md +45 -25
- package/dist/Chat/ChatTools.js +30 -16
- package/dist/Chat/ChatTools.js.map +1 -1
- package/dist/Chat/hostedToolValidation.generated.d.ts +46 -0
- package/dist/Chat/hostedToolValidation.generated.js +306 -0
- package/dist/Chat/hostedToolValidation.generated.js.map +1 -0
- package/dist/Chat/index.d.ts +41 -1
- package/dist/Chat/index.js +285 -26
- package/dist/Chat/index.js.map +1 -1
- package/dist/Chat/modelRouting.d.ts +8 -26
- package/dist/Chat/modelRouting.js +11 -106
- package/dist/Chat/modelRouting.js.map +1 -1
- package/dist/Chat/sogniHostedTools.generated.json +1596 -304
- package/dist/Chat/tools.d.ts +52 -3
- package/dist/Chat/tools.js +80 -27
- package/dist/Chat/tools.js.map +1 -1
- package/dist/Chat/types.d.ts +119 -3
- package/dist/CreativeWorkflows/index.d.ts +1 -7
- package/dist/CreativeWorkflows/index.js +60 -21
- package/dist/CreativeWorkflows/index.js.map +1 -1
- package/dist/CreativeWorkflows/types.d.ts +28 -33
- package/dist/Projects/index.d.ts +49 -8
- package/dist/Projects/index.js +49 -8
- package/dist/Projects/index.js.map +1 -1
- package/dist/index.d.ts +19 -4
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/Cache.js +6 -1
- package/dist/lib/Cache.js.map +1 -1
- package/dist/lib/mediaValidation.d.ts +3 -0
- package/dist/lib/mediaValidation.js +154 -0
- package/dist/lib/mediaValidation.js.map +1 -1
- package/dist-esm/Account/CurrentAccount.js +59 -0
- package/dist-esm/Account/CurrentAccount.js.map +1 -0
- package/dist-esm/Account/index.js +543 -0
- package/dist-esm/Account/index.js.map +1 -0
- package/dist-esm/Account/types.js +2 -0
- package/dist-esm/Account/types.js.map +1 -0
- package/dist-esm/ApiClient/WebSocketClient/BrowserWebSocketClient/ChannelCoordinator.js +327 -0
- package/dist-esm/ApiClient/WebSocketClient/BrowserWebSocketClient/ChannelCoordinator.js.map +1 -0
- package/dist-esm/ApiClient/WebSocketClient/BrowserWebSocketClient/index.js +222 -0
- package/dist-esm/ApiClient/WebSocketClient/BrowserWebSocketClient/index.js.map +1 -0
- package/dist-esm/ApiClient/WebSocketClient/ErrorCode.js +15 -0
- package/dist-esm/ApiClient/WebSocketClient/ErrorCode.js.map +1 -0
- package/dist-esm/ApiClient/WebSocketClient/eventSubscriptions.js +35 -0
- package/dist-esm/ApiClient/WebSocketClient/eventSubscriptions.js.map +1 -0
- package/dist-esm/ApiClient/WebSocketClient/events.js +2 -0
- package/dist-esm/ApiClient/WebSocketClient/events.js.map +1 -0
- package/dist-esm/ApiClient/WebSocketClient/index.js +221 -0
- package/dist-esm/ApiClient/WebSocketClient/index.js.map +1 -0
- package/dist-esm/ApiClient/WebSocketClient/messages.js +2 -0
- package/dist-esm/ApiClient/WebSocketClient/messages.js.map +1 -0
- package/dist-esm/ApiClient/WebSocketClient/types.js +2 -0
- package/dist-esm/ApiClient/WebSocketClient/types.js.map +1 -0
- package/dist-esm/ApiClient/events.js +2 -0
- package/dist-esm/ApiClient/events.js.map +1 -0
- package/dist-esm/ApiClient/index.js +120 -0
- package/dist-esm/ApiClient/index.js.map +1 -0
- package/dist-esm/ApiGroup.js +10 -0
- package/dist-esm/ApiGroup.js.map +1 -0
- package/dist-esm/Chat/ChatStream.js +181 -0
- package/dist-esm/Chat/ChatStream.js.map +1 -0
- package/dist-esm/Chat/ChatTools.js +562 -0
- package/dist-esm/Chat/ChatTools.js.map +1 -0
- package/dist-esm/Chat/hostedToolValidation.generated.js +301 -0
- package/dist-esm/Chat/hostedToolValidation.generated.js.map +1 -0
- package/dist-esm/Chat/index.js +693 -0
- package/dist-esm/Chat/index.js.map +1 -0
- package/dist-esm/Chat/modelRouting.js +326 -0
- package/dist-esm/Chat/modelRouting.js.map +1 -0
- package/dist-esm/Chat/sogniHostedTools.generated.json +1821 -0
- package/dist-esm/Chat/tools.js +149 -0
- package/dist-esm/Chat/tools.js.map +1 -0
- package/dist-esm/Chat/types.js +2 -0
- package/dist-esm/Chat/types.js.map +1 -0
- package/dist-esm/CreativeWorkflows/index.js +293 -0
- package/dist-esm/CreativeWorkflows/index.js.map +1 -0
- package/dist-esm/CreativeWorkflows/types.js +2 -0
- package/dist-esm/CreativeWorkflows/types.js.map +1 -0
- package/dist-esm/Projects/Job.js +391 -0
- package/dist-esm/Projects/Job.js.map +1 -0
- package/dist-esm/Projects/Project.js +295 -0
- package/dist-esm/Projects/Project.js.map +1 -0
- package/dist-esm/Projects/createJobRequestMessage.js +528 -0
- package/dist-esm/Projects/createJobRequestMessage.js.map +1 -0
- package/dist-esm/Projects/index.js +1350 -0
- package/dist-esm/Projects/index.js.map +1 -0
- package/dist-esm/Projects/types/ComfySamplerParams.js +2 -0
- package/dist-esm/Projects/types/ComfySamplerParams.js.map +1 -0
- package/dist-esm/Projects/types/ControlNetParams.js +2 -0
- package/dist-esm/Projects/types/ControlNetParams.js.map +1 -0
- package/dist-esm/Projects/types/EstimationResponse.js +2 -0
- package/dist-esm/Projects/types/EstimationResponse.js.map +1 -0
- package/dist-esm/Projects/types/ModelOptions.js +89 -0
- package/dist-esm/Projects/types/ModelOptions.js.map +1 -0
- package/dist-esm/Projects/types/ModelTiersRaw.js +13 -0
- package/dist-esm/Projects/types/ModelTiersRaw.js.map +1 -0
- package/dist-esm/Projects/types/RawProject.js +2 -0
- package/dist-esm/Projects/types/RawProject.js.map +1 -0
- package/dist-esm/Projects/types/events.js +2 -0
- package/dist-esm/Projects/types/events.js.map +1 -0
- package/dist-esm/Projects/types/index.js +10 -0
- package/dist-esm/Projects/types/index.js.map +1 -0
- package/dist-esm/Projects/utils/index.js +229 -0
- package/dist-esm/Projects/utils/index.js.map +1 -0
- package/dist-esm/Projects/utils/samplers.js +36 -0
- package/dist-esm/Projects/utils/samplers.js.map +1 -0
- package/dist-esm/Projects/utils/scheduler.js +27 -0
- package/dist-esm/Projects/utils/scheduler.js.map +1 -0
- package/dist-esm/Stats/index.js +20 -0
- package/dist-esm/Stats/index.js.map +1 -0
- package/dist-esm/Stats/types.js +2 -0
- package/dist-esm/Stats/types.js.map +1 -0
- package/dist-esm/events.js +2 -0
- package/dist-esm/events.js.map +1 -0
- package/dist-esm/index.js +144 -0
- package/dist-esm/index.js.map +1 -0
- package/dist-esm/lib/AuthManager/ApiKeyAuthManager.js +58 -0
- package/dist-esm/lib/AuthManager/ApiKeyAuthManager.js.map +1 -0
- package/dist-esm/lib/AuthManager/AuthManagerBase.js +9 -0
- package/dist-esm/lib/AuthManager/AuthManagerBase.js.map +1 -0
- package/dist-esm/lib/AuthManager/CookieAuthManager.js +48 -0
- package/dist-esm/lib/AuthManager/CookieAuthManager.js.map +1 -0
- package/dist-esm/lib/AuthManager/TokenAuthManager.js +186 -0
- package/dist-esm/lib/AuthManager/TokenAuthManager.js.map +1 -0
- package/dist-esm/lib/AuthManager/index.js +5 -0
- package/dist-esm/lib/AuthManager/index.js.map +1 -0
- package/dist-esm/lib/Cache.js +32 -0
- package/dist-esm/lib/Cache.js.map +1 -0
- package/dist-esm/lib/DataEntity.js +30 -0
- package/dist-esm/lib/DataEntity.js.map +1 -0
- package/dist-esm/lib/DefaultLogger.js +32 -0
- package/dist-esm/lib/DefaultLogger.js.map +1 -0
- package/dist-esm/lib/EIP712Helper.js +37 -0
- package/dist-esm/lib/EIP712Helper.js.map +1 -0
- package/dist-esm/lib/RestClient.js +90 -0
- package/dist-esm/lib/RestClient.js.map +1 -0
- package/dist-esm/lib/TypedEventEmitter.js +68 -0
- package/dist-esm/lib/TypedEventEmitter.js.map +1 -0
- package/dist-esm/lib/base64.js +18 -0
- package/dist-esm/lib/base64.js.map +1 -0
- package/dist-esm/lib/getUUID.js +7 -0
- package/dist-esm/lib/getUUID.js.map +1 -0
- package/dist-esm/lib/isNodejs.js +3 -0
- package/dist-esm/lib/isNodejs.js.map +1 -0
- package/dist-esm/lib/mediaValidation.js +430 -0
- package/dist-esm/lib/mediaValidation.js.map +1 -0
- package/dist-esm/lib/utils/index.js +30 -0
- package/dist-esm/lib/utils/index.js.map +1 -0
- package/dist-esm/lib/validation.js +138 -0
- package/dist-esm/lib/validation.js.map +1 -0
- package/dist-esm/package.json +3 -0
- package/dist-esm/types/ErrorData.js +2 -0
- package/dist-esm/types/ErrorData.js.map +1 -0
- package/dist-esm/types/json.js +2 -0
- package/dist-esm/types/json.js.map +1 -0
- package/dist-esm/types/token.js +2 -0
- package/dist-esm/types/token.js.map +1 -0
- package/dist-esm/version.js +3 -0
- package/dist-esm/version.js.map +1 -0
- package/llms-full.txt +11 -20
- package/llms.txt +9 -18
- package/package.json +21 -5
- package/src/Chat/ChatTools.ts +36 -16
- package/src/Chat/hostedToolValidation.generated.ts +453 -0
- package/src/Chat/index.ts +322 -37
- package/src/Chat/modelRouting.ts +26 -150
- package/src/Chat/sogniHostedTools.generated.json +1596 -304
- package/src/Chat/tools.ts +106 -41
- package/src/Chat/types.ts +131 -3
- package/src/CreativeWorkflows/index.ts +55 -41
- package/src/CreativeWorkflows/types.ts +70 -42
- package/src/Projects/index.ts +49 -8
- package/src/index.ts +33 -12
- package/src/lib/Cache.ts +8 -1
- package/src/lib/mediaValidation.ts +155 -0
package/src/Projects/index.ts
CHANGED
|
@@ -1223,8 +1223,26 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
1223
1223
|
// ============================================
|
|
1224
1224
|
|
|
1225
1225
|
/**
|
|
1226
|
-
*
|
|
1227
|
-
*
|
|
1226
|
+
* Request a presigned upload URL for an image asset (reference image,
|
|
1227
|
+
* starting image, ControlNet image, context image, etc.). The caller
|
|
1228
|
+
* uploads the image bytes via `PUT` to the returned URL before
|
|
1229
|
+
* starting a project or workflow that references the asset.
|
|
1230
|
+
*
|
|
1231
|
+
* @param {ImageUrlParams} params - Image asset coordinates:
|
|
1232
|
+
* - imageId: Stable identifier for the asset within the job
|
|
1233
|
+
* (e.g. `"media_ref_1"`). The same id is later used to reference
|
|
1234
|
+
* the asset in workflow inputs.
|
|
1235
|
+
* - jobId: Caller-generated job/correlation id (e.g.
|
|
1236
|
+
* `"sogni-agent-1735000000-1-abcdef"`). Ties the asset to a
|
|
1237
|
+
* specific request.
|
|
1238
|
+
* - type: Asset role. Supported values include `'referenceImage'`,
|
|
1239
|
+
* `'referenceImageEnd'`, `'startingImage'`, `'cnImage'`,
|
|
1240
|
+
* `'contextImage1'`..`'contextImage16'`, `'preview'`, `'complete'`.
|
|
1241
|
+
* - contentType: Optional MIME type the caller will `PUT` (e.g.
|
|
1242
|
+
* `"image/png"`). Forwarded so the storage layer can pin the
|
|
1243
|
+
* Content-Type on the presigned URL.
|
|
1244
|
+
* @return {Promise<string>} Presigned `PUT` URL the caller should
|
|
1245
|
+
* upload the image bytes to. Short-lived; use immediately.
|
|
1228
1246
|
*/
|
|
1229
1247
|
async uploadUrl(params: ImageUrlParams) {
|
|
1230
1248
|
const r = await this.client.rest.get<ApiResponse<{ uploadUrl: string }>>(
|
|
@@ -1235,8 +1253,13 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
1235
1253
|
}
|
|
1236
1254
|
|
|
1237
1255
|
/**
|
|
1238
|
-
*
|
|
1239
|
-
*
|
|
1256
|
+
* Request a presigned download URL for a stored image asset.
|
|
1257
|
+
*
|
|
1258
|
+
* @param {ImageUrlParams} params - Same shape as
|
|
1259
|
+
* {@link ProjectsApi.uploadUrl}; `imageId`, `jobId`, and `type`
|
|
1260
|
+
* must match the values used at upload time.
|
|
1261
|
+
* @return {Promise<string>} Presigned `GET` URL for the image.
|
|
1262
|
+
* Throws if the server response does not include a `downloadUrl`.
|
|
1240
1263
|
*/
|
|
1241
1264
|
async downloadUrl(params: ImageUrlParams) {
|
|
1242
1265
|
const r = await this.client.rest.get<ApiResponse<{ downloadUrl: string }>>(
|
|
@@ -1250,8 +1273,21 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
1250
1273
|
}
|
|
1251
1274
|
|
|
1252
1275
|
/**
|
|
1253
|
-
*
|
|
1254
|
-
*
|
|
1276
|
+
* Request a presigned upload URL for an audio or video asset
|
|
1277
|
+
* (reference audio, reference video, finished media artifacts, etc.).
|
|
1278
|
+
* The caller uploads the media bytes via `PUT` to the returned URL
|
|
1279
|
+
* before starting a project or workflow that references the asset.
|
|
1280
|
+
*
|
|
1281
|
+
* @param {MediaUrlParams} params - Media asset coordinates:
|
|
1282
|
+
* - id: Stable identifier for the asset within the job
|
|
1283
|
+
* (e.g. `"media_ref_1"`). Optional for some asset roles.
|
|
1284
|
+
* - jobId: Caller-generated job/correlation id.
|
|
1285
|
+
* - type: Asset role. Supported values are `'referenceAudio'`,
|
|
1286
|
+
* `'referenceVideo'`, `'preview'`, `'complete'`.
|
|
1287
|
+
* - contentType: Optional MIME type the caller will `PUT`
|
|
1288
|
+
* (e.g. `"audio/mp4"` or `"video/mp4"`).
|
|
1289
|
+
* @return {Promise<string>} Presigned `PUT` URL the caller should
|
|
1290
|
+
* upload the media bytes to.
|
|
1255
1291
|
*/
|
|
1256
1292
|
async mediaUploadUrl(params: MediaUrlParams) {
|
|
1257
1293
|
const r = await this.client.rest.get<ApiResponse<{ uploadUrl: string }>>(
|
|
@@ -1262,8 +1298,13 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
1262
1298
|
}
|
|
1263
1299
|
|
|
1264
1300
|
/**
|
|
1265
|
-
*
|
|
1266
|
-
*
|
|
1301
|
+
* Request a presigned download URL for a stored audio or video asset.
|
|
1302
|
+
*
|
|
1303
|
+
* @param {MediaUrlParams} params - Same shape as
|
|
1304
|
+
* {@link ProjectsApi.mediaUploadUrl}; `id`, `jobId`, and `type`
|
|
1305
|
+
* must match the values used at upload time.
|
|
1306
|
+
* @return {Promise<string>} Presigned `GET` URL for the media.
|
|
1307
|
+
* Throws if the server response does not include a `downloadUrl`.
|
|
1267
1308
|
*/
|
|
1268
1309
|
async mediaDownloadUrl(params: MediaUrlParams) {
|
|
1269
1310
|
const r = await this.client.rest.get<ApiResponse<{ downloadUrl: string }>>(
|
package/src/index.ts
CHANGED
|
@@ -53,6 +53,11 @@ import {
|
|
|
53
53
|
ChatCompletionResult,
|
|
54
54
|
ChatJobStateEvent,
|
|
55
55
|
ChatResponseFormat,
|
|
56
|
+
ChatRunEvent,
|
|
57
|
+
ChatRunRecord,
|
|
58
|
+
ChatRunStatus,
|
|
59
|
+
StartChatRunParams,
|
|
60
|
+
StreamChatRunEventsOptions,
|
|
56
61
|
ContentPart,
|
|
57
62
|
HostedChatCompletionParams,
|
|
58
63
|
HostedChatCompletionResult,
|
|
@@ -85,7 +90,6 @@ import CreativeWorkflowsApi, { parseCreativeWorkflowSseChunk } from './CreativeW
|
|
|
85
90
|
import {
|
|
86
91
|
CreativeWorkflowArtifact,
|
|
87
92
|
CreativeWorkflowEvent,
|
|
88
|
-
CreativeWorkflowKind,
|
|
89
93
|
CreativeWorkflowRecord,
|
|
90
94
|
CreativeWorkflowSseEvent,
|
|
91
95
|
CreativeWorkflowStatus,
|
|
@@ -93,10 +97,9 @@ import {
|
|
|
93
97
|
ListCreativeWorkflowOptions,
|
|
94
98
|
StartCreativeWorkflowOptions,
|
|
95
99
|
StartCreativeWorkflowParams,
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
StartImageToVideoWorkflowInput,
|
|
100
|
+
StartCreativeWorkflowDependency,
|
|
101
|
+
StartCreativeWorkflowInput,
|
|
102
|
+
StartCreativeWorkflowStep,
|
|
100
103
|
StreamCreativeWorkflowEventsOptions
|
|
101
104
|
} from './CreativeWorkflows/types';
|
|
102
105
|
// Stats API
|
|
@@ -123,7 +126,12 @@ export type {
|
|
|
123
126
|
ChatJobStateEvent,
|
|
124
127
|
ChatMessage,
|
|
125
128
|
ChatResponseFormat,
|
|
129
|
+
ChatRunEvent,
|
|
130
|
+
ChatRunRecord,
|
|
131
|
+
ChatRunStatus,
|
|
126
132
|
ChatTokenUsage,
|
|
133
|
+
StartChatRunParams,
|
|
134
|
+
StreamChatRunEventsOptions,
|
|
127
135
|
ContentPart,
|
|
128
136
|
HostedChatCompletionChoice,
|
|
129
137
|
HostedChatCompletionMessage,
|
|
@@ -132,7 +140,6 @@ export type {
|
|
|
132
140
|
HostedCreativeWorkflowReference,
|
|
133
141
|
CreativeWorkflowArtifact,
|
|
134
142
|
CreativeWorkflowEvent,
|
|
135
|
-
CreativeWorkflowKind,
|
|
136
143
|
CreativeWorkflowRecord,
|
|
137
144
|
CreativeWorkflowSseEvent,
|
|
138
145
|
CreativeWorkflowStatus,
|
|
@@ -158,10 +165,9 @@ export type {
|
|
|
158
165
|
CreativeWorkflowHostedToolName,
|
|
159
166
|
StartCreativeWorkflowOptions,
|
|
160
167
|
StartCreativeWorkflowParams,
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
StartImageToVideoWorkflowInput,
|
|
168
|
+
StartCreativeWorkflowDependency,
|
|
169
|
+
StartCreativeWorkflowInput,
|
|
170
|
+
StartCreativeWorkflowStep,
|
|
165
171
|
StreamCreativeWorkflowEventsOptions,
|
|
166
172
|
SupernetType,
|
|
167
173
|
TokenType,
|
|
@@ -292,8 +298,23 @@ export class SogniClient {
|
|
|
292
298
|
account: AccountApi;
|
|
293
299
|
projects: ProjectsApi;
|
|
294
300
|
stats: StatsApi;
|
|
301
|
+
/**
|
|
302
|
+
* Chat surfaces.
|
|
303
|
+
* - `chat.completions.create` — socket-native chat completion.
|
|
304
|
+
* - `chat.hosted.create` — hosted REST chat completion (synchronous).
|
|
305
|
+
* - `chat.runs.{create, get, cancel, streamEvents}` — durable hosted
|
|
306
|
+
* chat runs that survive client disconnect, browser close, network
|
|
307
|
+
* drop, and API worker restart. See `/v1/chat/runs` REST surface.
|
|
308
|
+
* - `chat.tools.execute*` — execute Sogni platform tools surfaced by
|
|
309
|
+
* the chat completion (image / video / music generation).
|
|
310
|
+
*/
|
|
295
311
|
chat: ChatApi;
|
|
296
|
-
|
|
312
|
+
/**
|
|
313
|
+
* Durable creative workflows (`/v1/creative-agent/workflows`). Submit
|
|
314
|
+
* an explicit step sequence and follow its progress without keeping
|
|
315
|
+
* the client connected.
|
|
316
|
+
*/
|
|
317
|
+
workflows: CreativeWorkflowsApi;
|
|
297
318
|
|
|
298
319
|
apiClient: ApiClient;
|
|
299
320
|
|
|
@@ -302,7 +323,7 @@ export class SogniClient {
|
|
|
302
323
|
this.projects = new ProjectsApi(config);
|
|
303
324
|
this.stats = new StatsApi(config);
|
|
304
325
|
this.chat = new ChatApi(config, this.projects);
|
|
305
|
-
this.
|
|
326
|
+
this.workflows = new CreativeWorkflowsApi(config);
|
|
306
327
|
|
|
307
328
|
this.apiClient = config.client;
|
|
308
329
|
}
|
package/src/lib/Cache.ts
CHANGED
|
@@ -10,7 +10,14 @@ export default class Cache<V = any> {
|
|
|
10
10
|
|
|
11
11
|
constructor(defaultTTL: number) {
|
|
12
12
|
this.ttl = defaultTTL;
|
|
13
|
-
setInterval(() => this.cleanup(), 10000)
|
|
13
|
+
const timer = setInterval(() => this.cleanup(), 10000) as unknown as {
|
|
14
|
+
unref?: () => void;
|
|
15
|
+
};
|
|
16
|
+
// Node timers have unref() so a module-level Cache doesn't keep one-shot
|
|
17
|
+
// scripts alive. Browser timers return numbers and don't expose unref.
|
|
18
|
+
if (typeof timer.unref === 'function') {
|
|
19
|
+
timer.unref();
|
|
20
|
+
}
|
|
14
21
|
}
|
|
15
22
|
|
|
16
23
|
write(key: string, value: V, ttl?: number) {
|
|
@@ -8,6 +8,8 @@ export interface ImageDimensions {
|
|
|
8
8
|
export interface InlineMediaValidationOptions {
|
|
9
9
|
maxBytes?: number;
|
|
10
10
|
maxImageLongestSide?: number;
|
|
11
|
+
remoteFetchTimeoutMs?: number;
|
|
12
|
+
signal?: AbortSignal;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
export interface ParsedInlineMediaData {
|
|
@@ -320,6 +322,109 @@ function validateMagicBytes(
|
|
|
320
322
|
return undefined;
|
|
321
323
|
}
|
|
322
324
|
|
|
325
|
+
function mimeTypeForDetectedFormat(mediaType: MediaType, bytes: Uint8Array): string {
|
|
326
|
+
if (mediaType === 'image') {
|
|
327
|
+
const format = detectImageFormat(bytes);
|
|
328
|
+
if (format === 'png') return 'image/png';
|
|
329
|
+
if (format === 'jpeg') return 'image/jpeg';
|
|
330
|
+
} else if (mediaType === 'audio') {
|
|
331
|
+
const format = detectAudioFormat(bytes);
|
|
332
|
+
if (format === 'wav') return 'audio/wav';
|
|
333
|
+
if (format === 'mpeg') return 'audio/mpeg';
|
|
334
|
+
if (format === 'mp4') return 'audio/mp4';
|
|
335
|
+
} else {
|
|
336
|
+
const format = detectVideoFormat(bytes);
|
|
337
|
+
if (format === 'quicktime') return 'video/quicktime';
|
|
338
|
+
if (format === 'mp4') return 'video/mp4';
|
|
339
|
+
}
|
|
340
|
+
throw new Error(
|
|
341
|
+
`Remote ${mediaType} input is not a supported format. Allowed types: ${getAllowedMimeTypes(mediaType).join(', ')}`
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function normalizeResponseMimeType(
|
|
346
|
+
mediaType: MediaType,
|
|
347
|
+
contentType: string | null,
|
|
348
|
+
bytes: Uint8Array
|
|
349
|
+
): string {
|
|
350
|
+
const mimeType = contentType?.split(';')[0]?.trim().toLowerCase() || '';
|
|
351
|
+
if (getAllowedMimeTypes(mediaType).includes(mimeType)) return mimeType;
|
|
352
|
+
return mimeTypeForDetectedFormat(mediaType, bytes);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function throwMediaByteLimit(mediaType: MediaType, maxBytes: number): never {
|
|
356
|
+
throw new Error(`${mediaType} input exceeds ${Math.round(maxBytes / (1024 * 1024))}MB limit`);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async function readResponseBytes(
|
|
360
|
+
response: Response,
|
|
361
|
+
mediaType: MediaType,
|
|
362
|
+
maxBytes: number | undefined
|
|
363
|
+
): Promise<Uint8Array> {
|
|
364
|
+
if (!response.body) {
|
|
365
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
366
|
+
if (maxBytes !== undefined && bytes.length > maxBytes) {
|
|
367
|
+
throwMediaByteLimit(mediaType, maxBytes);
|
|
368
|
+
}
|
|
369
|
+
return bytes;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const reader = response.body.getReader();
|
|
373
|
+
const chunks: Uint8Array[] = [];
|
|
374
|
+
let totalBytes = 0;
|
|
375
|
+
|
|
376
|
+
try {
|
|
377
|
+
while (true) {
|
|
378
|
+
const { value, done } = await reader.read();
|
|
379
|
+
if (done) break;
|
|
380
|
+
if (!value) continue;
|
|
381
|
+
|
|
382
|
+
totalBytes += value.byteLength;
|
|
383
|
+
if (maxBytes !== undefined && totalBytes > maxBytes) {
|
|
384
|
+
try {
|
|
385
|
+
await reader.cancel();
|
|
386
|
+
} catch {
|
|
387
|
+
// Preserve the size-limit error below.
|
|
388
|
+
}
|
|
389
|
+
throwMediaByteLimit(mediaType, maxBytes);
|
|
390
|
+
}
|
|
391
|
+
chunks.push(value);
|
|
392
|
+
}
|
|
393
|
+
} finally {
|
|
394
|
+
reader.releaseLock();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const bytes = new Uint8Array(totalBytes);
|
|
398
|
+
let offset = 0;
|
|
399
|
+
for (const chunk of chunks) {
|
|
400
|
+
bytes.set(chunk, offset);
|
|
401
|
+
offset += chunk.byteLength;
|
|
402
|
+
}
|
|
403
|
+
return bytes;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const DEFAULT_REMOTE_MEDIA_FETCH_TIMEOUT_MS = 20_000;
|
|
407
|
+
|
|
408
|
+
function trustedRemoteMediaUrl(value: string): URL | null {
|
|
409
|
+
try {
|
|
410
|
+
const url = new URL(value.trim());
|
|
411
|
+
if (url.protocol !== 'https:') return null;
|
|
412
|
+
if (url.username || url.password) return null;
|
|
413
|
+
const host = url.hostname.toLowerCase();
|
|
414
|
+
const trusted =
|
|
415
|
+
host === 'cdn.sogni.ai' ||
|
|
416
|
+
host.endsWith('.sogni.ai') ||
|
|
417
|
+
host === 'complete-images-production.s3.amazonaws.com' ||
|
|
418
|
+
/^[a-z0-9.-]+\.s3\.amazonaws\.com$/i.test(host) ||
|
|
419
|
+
/^[a-z0-9.-]+\.s3\.[a-z0-9-]+\.amazonaws\.com$/i.test(host) ||
|
|
420
|
+
/^s3\.[a-z0-9-]+\.amazonaws\.com$/i.test(host) ||
|
|
421
|
+
host.endsWith('.cloudfront.net');
|
|
422
|
+
return trusted ? url : null;
|
|
423
|
+
} catch {
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
323
428
|
function validateImageDimensions(dimensions: ImageDimensions, maxLongestSide: number): void {
|
|
324
429
|
if (Math.max(dimensions.width, dimensions.height) > maxLongestSide) {
|
|
325
430
|
throw new Error(
|
|
@@ -365,3 +470,53 @@ export function parseInlineMediaDataUri(
|
|
|
365
470
|
imageDimensions
|
|
366
471
|
};
|
|
367
472
|
}
|
|
473
|
+
|
|
474
|
+
export async function mediaInputToInlineDataUri(
|
|
475
|
+
input: string,
|
|
476
|
+
mediaType: MediaType,
|
|
477
|
+
options: InlineMediaValidationOptions = {}
|
|
478
|
+
): Promise<string> {
|
|
479
|
+
const trimmed = input.trim();
|
|
480
|
+
const remoteUrl = trustedRemoteMediaUrl(trimmed);
|
|
481
|
+
if (!remoteUrl) {
|
|
482
|
+
parseInlineMediaDataUri(trimmed, mediaType, options);
|
|
483
|
+
return trimmed;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const controller = new AbortController();
|
|
487
|
+
const timeoutMs = options.remoteFetchTimeoutMs ?? DEFAULT_REMOTE_MEDIA_FETCH_TIMEOUT_MS;
|
|
488
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
489
|
+
const abortFromParent = () => controller.abort();
|
|
490
|
+
options.signal?.addEventListener('abort', abortFromParent, { once: true });
|
|
491
|
+
try {
|
|
492
|
+
if (options.signal?.aborted) controller.abort();
|
|
493
|
+
const response = await fetch(remoteUrl.toString(), {
|
|
494
|
+
signal: controller.signal,
|
|
495
|
+
redirect: 'error'
|
|
496
|
+
});
|
|
497
|
+
if (!response.ok) {
|
|
498
|
+
throw new Error(`Remote ${mediaType} input fetch failed with HTTP ${response.status}`);
|
|
499
|
+
}
|
|
500
|
+
const contentLength = Number(response.headers.get('content-length') || '');
|
|
501
|
+
if (
|
|
502
|
+
options.maxBytes !== undefined &&
|
|
503
|
+
Number.isFinite(contentLength) &&
|
|
504
|
+
contentLength > options.maxBytes
|
|
505
|
+
) {
|
|
506
|
+
throwMediaByteLimit(mediaType, options.maxBytes);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const bytes = await readResponseBytes(response, mediaType, options.maxBytes);
|
|
510
|
+
const mimeType = normalizeResponseMimeType(
|
|
511
|
+
mediaType,
|
|
512
|
+
response.headers.get('content-type'),
|
|
513
|
+
bytes
|
|
514
|
+
);
|
|
515
|
+
const dataUri = `data:${mimeType};base64,${encodeBase64(bytes)}`;
|
|
516
|
+
parseInlineMediaDataUri(dataUri, mediaType, options);
|
|
517
|
+
return dataUri;
|
|
518
|
+
} finally {
|
|
519
|
+
clearTimeout(timeoutId);
|
|
520
|
+
options.signal?.removeEventListener('abort', abortFromParent);
|
|
521
|
+
}
|
|
522
|
+
}
|