ugcinc 3.85.1 → 3.87.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.
@@ -1,5 +1,20 @@
1
- import type { NodeControlConfig, NodeCategory, MediaType, NodePort, NodeTypeEnum, WorkflowDefinition, AutomationTemplate, AutomationRun, ExecutorNode, ExecutionEdge, ApiResponse, AutomationExport, AutomationRunExport, PropertySchema } from './types';
1
+ import type { NodeControlConfig, NodeCategory, MediaType, NodePort, NodeTypeEnum, PortType, WorkflowDefinition, AutomationTemplate, AutomationRun, ExecutorNode, ExecutionEdge, ApiResponse, AutomationExport, AutomationRunExport, PropertySchema } from './types';
2
2
  import { BaseClient } from './base';
3
+ /**
4
+ * Check if two port types are compatible for connection
5
+ * A connection is valid if:
6
+ * 1. The types match exactly, OR
7
+ * 2. The source is an array type and target is 'object', OR
8
+ * 3. The source is a PortType[] and includes the target type, OR
9
+ * 4. The target is a PortType[] and includes the source type, OR
10
+ * 5. Both are PortType[] and have at least one common type
11
+ *
12
+ * Note: Array inputs are strict - they only accept exact matches
13
+ */
14
+ export declare function areTypesCompatible({ sourceType, targetType, }: {
15
+ sourceType: PortType | PortType[];
16
+ targetType: PortType | PortType[];
17
+ }): boolean;
3
18
  export declare class AutomationsClient extends BaseClient {
4
19
  /**
5
20
  * List all automation templates for an organization
@@ -1,10 +1,46 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AutomationsClient = void 0;
4
+ exports.areTypesCompatible = areTypesCompatible;
4
5
  exports.getAllNodes = getAllNodes;
5
6
  exports.getNodeByType = getNodeByType;
6
7
  exports.getOutputSchema = getOutputSchema;
7
8
  const base_1 = require("./base");
9
+ /**
10
+ * Array types for collections
11
+ */
12
+ const ARRAY_TYPES = ['image[]', 'video[]', 'audio[]', 'text[]', 'object[]', 'account[]', 'social_audio[]', 'boolean[]'];
13
+ /**
14
+ * Check if two port types are compatible for connection
15
+ * A connection is valid if:
16
+ * 1. The types match exactly, OR
17
+ * 2. The source is an array type and target is 'object', OR
18
+ * 3. The source is a PortType[] and includes the target type, OR
19
+ * 4. The target is a PortType[] and includes the source type, OR
20
+ * 5. Both are PortType[] and have at least one common type
21
+ *
22
+ * Note: Array inputs are strict - they only accept exact matches
23
+ */
24
+ function areTypesCompatible({ sourceType, targetType, }) {
25
+ const sourceTypes = Array.isArray(sourceType) ? sourceType : [sourceType];
26
+ const targetTypes = Array.isArray(targetType) ? targetType : [targetType];
27
+ return sourceTypes.some(st => {
28
+ return targetTypes.some(tt => {
29
+ // Exact match
30
+ if (st === tt)
31
+ return true;
32
+ // Object inputs accept any array type (backwards compatibility with for-each, etc.)
33
+ if (tt === 'object' && ARRAY_TYPES.includes(st)) {
34
+ return true;
35
+ }
36
+ // Array inputs are strict - no other compatibility
37
+ if (ARRAY_TYPES.includes(tt)) {
38
+ return false;
39
+ }
40
+ return false;
41
+ });
42
+ });
43
+ }
8
44
  class AutomationsClient extends base_1.BaseClient {
9
45
  /**
10
46
  * List all automation templates for an organization
package/dist/index.d.ts CHANGED
@@ -10,7 +10,9 @@ export { PostsClient } from './posts';
10
10
  export { StatsClient } from './stats';
11
11
  export { OrganizationClient } from './org';
12
12
  export { RenderClient } from './render';
13
- export { AutomationsClient, getAllNodes, getNodeByType, getOutputSchema } from './automations';
13
+ export { AutomationsClient, getAllNodes, getNodeByType, getOutputSchema, areTypesCompatible } from './automations';
14
+ export { computeInputPorts, computeOutputPorts, isInputPortRequired, extractTemplateVariables } from './ports';
15
+ export type { ComputedPort } from './ports';
14
16
  export { NodeTypes, isAsyncExecutor, isEditModel, isImageToVideoModel } from './types';
15
17
  export type { PropertySchema } from './automations';
16
18
  export { portId, isValidPortId, portIdToTitle, normalizeToPortId, PortIdSchema } from './port-id';
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * Official TypeScript/JavaScript client for the UGC Inc API
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.CommentsClient = exports.MediaClient = exports.PortIdSchema = exports.normalizeToPortId = exports.portIdToTitle = exports.isValidPortId = exports.portId = exports.isImageToVideoModel = exports.isEditModel = exports.isAsyncExecutor = exports.NodeTypes = exports.getOutputSchema = exports.getNodeByType = exports.getAllNodes = exports.AutomationsClient = exports.RenderClient = exports.OrganizationClient = exports.StatsClient = exports.PostsClient = exports.TasksClient = exports.AccountsClient = exports.UGCClient = void 0;
8
+ exports.CommentsClient = exports.MediaClient = exports.PortIdSchema = exports.normalizeToPortId = exports.portIdToTitle = exports.isValidPortId = exports.portId = exports.isImageToVideoModel = exports.isEditModel = exports.isAsyncExecutor = exports.NodeTypes = exports.extractTemplateVariables = exports.isInputPortRequired = exports.computeOutputPorts = exports.computeInputPorts = exports.areTypesCompatible = exports.getOutputSchema = exports.getNodeByType = exports.getAllNodes = exports.AutomationsClient = exports.RenderClient = exports.OrganizationClient = exports.StatsClient = exports.PostsClient = exports.TasksClient = exports.AccountsClient = exports.UGCClient = void 0;
9
9
  var client_1 = require("./client");
10
10
  Object.defineProperty(exports, "UGCClient", { enumerable: true, get: function () { return client_1.UGCClient; } });
11
11
  var accounts_1 = require("./accounts");
@@ -25,6 +25,12 @@ Object.defineProperty(exports, "AutomationsClient", { enumerable: true, get: fun
25
25
  Object.defineProperty(exports, "getAllNodes", { enumerable: true, get: function () { return automations_1.getAllNodes; } });
26
26
  Object.defineProperty(exports, "getNodeByType", { enumerable: true, get: function () { return automations_1.getNodeByType; } });
27
27
  Object.defineProperty(exports, "getOutputSchema", { enumerable: true, get: function () { return automations_1.getOutputSchema; } });
28
+ Object.defineProperty(exports, "areTypesCompatible", { enumerable: true, get: function () { return automations_1.areTypesCompatible; } });
29
+ var ports_1 = require("./ports");
30
+ Object.defineProperty(exports, "computeInputPorts", { enumerable: true, get: function () { return ports_1.computeInputPorts; } });
31
+ Object.defineProperty(exports, "computeOutputPorts", { enumerable: true, get: function () { return ports_1.computeOutputPorts; } });
32
+ Object.defineProperty(exports, "isInputPortRequired", { enumerable: true, get: function () { return ports_1.isInputPortRequired; } });
33
+ Object.defineProperty(exports, "extractTemplateVariables", { enumerable: true, get: function () { return ports_1.extractTemplateVariables; } });
28
34
  var types_1 = require("./types");
29
35
  Object.defineProperty(exports, "NodeTypes", { enumerable: true, get: function () { return types_1.NodeTypes; } });
30
36
  Object.defineProperty(exports, "isAsyncExecutor", { enumerable: true, get: function () { return types_1.isAsyncExecutor; } });
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Port computation utilities for automation nodes
3
+ *
4
+ * These functions dynamically compute input and output ports for nodes
5
+ * based on their type and configuration. Used by both webapp (for UI rendering)
6
+ * and API (for validation and execution).
7
+ */
8
+ import type { PortType, NodeTypeEnum } from './types';
9
+ /**
10
+ * Computed port definition
11
+ */
12
+ export interface ComputedPort {
13
+ id: string;
14
+ type: PortType | PortType[];
15
+ required: boolean;
16
+ }
17
+ /**
18
+ * Extract template variables from text (e.g., "Hello {{name}}" -> ["name"])
19
+ */
20
+ export declare function extractTemplateVariables(texts: string[]): string[];
21
+ /**
22
+ * Minimal node interface for port computation
23
+ * Allows both WorkflowNodeDefinition and webapp's Node type to be used
24
+ */
25
+ interface NodeLike {
26
+ id: string;
27
+ type: NodeTypeEnum;
28
+ config?: Record<string, unknown>;
29
+ }
30
+ /**
31
+ * Compute the input ports for a node based on its type and config
32
+ */
33
+ export declare function computeInputPorts({ node }: {
34
+ node: NodeLike;
35
+ }): ComputedPort[];
36
+ /**
37
+ * Compute the output ports for a node based on its type and config
38
+ */
39
+ export declare function computeOutputPorts({ node }: {
40
+ node: NodeLike;
41
+ }): ComputedPort[];
42
+ /**
43
+ * Check if an input port is required for a given node.
44
+ */
45
+ export declare function isInputPortRequired({ node, portId, }: {
46
+ node: NodeLike;
47
+ portId: string;
48
+ }): boolean;
49
+ export {};
package/dist/ports.js ADDED
@@ -0,0 +1,470 @@
1
+ "use strict";
2
+ /**
3
+ * Port computation utilities for automation nodes
4
+ *
5
+ * These functions dynamically compute input and output ports for nodes
6
+ * based on their type and configuration. Used by both webapp (for UI rendering)
7
+ * and API (for validation and execution).
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.extractTemplateVariables = extractTemplateVariables;
11
+ exports.computeInputPorts = computeInputPorts;
12
+ exports.computeOutputPorts = computeOutputPorts;
13
+ exports.isInputPortRequired = isInputPortRequired;
14
+ const automations_1 = require("./automations");
15
+ /**
16
+ * Extract template variables from text (e.g., "Hello {{name}}" -> ["name"])
17
+ */
18
+ function extractTemplateVariables(texts) {
19
+ const variables = new Set();
20
+ const regex = /\{\{(\w+)\}\}/g;
21
+ for (const text of texts) {
22
+ let match;
23
+ while ((match = regex.exec(text)) !== null) {
24
+ if (match[1])
25
+ variables.add(match[1]);
26
+ }
27
+ }
28
+ return Array.from(variables);
29
+ }
30
+ /**
31
+ * Compute the input ports for a node based on its type and config
32
+ */
33
+ function computeInputPorts({ node }) {
34
+ const nodeConfigs = (0, automations_1.getAllNodes)();
35
+ const nodeConfig = nodeConfigs.find(n => n.type === node.type);
36
+ const baseInputs = nodeConfig?.inputs ?? [];
37
+ // Text nodes: generate inputs from template variables
38
+ if (node.type === 'text') {
39
+ const textOptions = (node.config?.textOptions ?? []);
40
+ const variables = extractTemplateVariables(textOptions);
41
+ if (variables.length === 0)
42
+ return baseInputs;
43
+ return variables.map(variable => ({
44
+ id: variable,
45
+ type: 'text',
46
+ required: false,
47
+ }));
48
+ }
49
+ // Image-editor nodes: generate inputs from elements
50
+ if (node.type === 'image-composer') {
51
+ const editorConfig = node.config?.imageEditor;
52
+ if (!editorConfig)
53
+ return baseInputs;
54
+ const dynamicInputs = [];
55
+ // Only add background input if backgroundType is 'image'
56
+ if (editorConfig.backgroundType !== 'color') {
57
+ dynamicInputs.push({
58
+ id: 'background',
59
+ type: 'image',
60
+ required: true,
61
+ });
62
+ }
63
+ // Add inputs for each element that has an inputId or textInputId
64
+ editorConfig.elements.forEach(elem => {
65
+ if (elem.type === 'image' && elem.inputId) {
66
+ dynamicInputs.push({
67
+ id: elem.inputId,
68
+ type: 'image',
69
+ required: true,
70
+ });
71
+ }
72
+ else if (elem.type === 'text' && elem.textInputId) {
73
+ dynamicInputs.push({
74
+ id: elem.textInputId,
75
+ type: 'text',
76
+ required: true,
77
+ });
78
+ }
79
+ });
80
+ // Add crop boundary inputs (not required)
81
+ const addedCropInputs = new Set();
82
+ const cropConfig = editorConfig.dynamicCrop;
83
+ [
84
+ cropConfig?.vertical?.startBoundary?.inputRef,
85
+ cropConfig?.vertical?.endBoundary?.inputRef,
86
+ cropConfig?.horizontal?.startBoundary?.inputRef,
87
+ cropConfig?.horizontal?.endBoundary?.inputRef,
88
+ ].forEach(inputRef => {
89
+ if (inputRef && !addedCropInputs.has(inputRef)) {
90
+ addedCropInputs.add(inputRef);
91
+ dynamicInputs.push({
92
+ id: inputRef,
93
+ type: 'text',
94
+ required: false,
95
+ });
96
+ }
97
+ });
98
+ return dynamicInputs;
99
+ }
100
+ // Video-editor nodes: generate inputs from segments
101
+ if (node.type === 'video-composer') {
102
+ const editorConfig = node.config?.videoEditor;
103
+ if (!editorConfig)
104
+ return [];
105
+ const dynamicInputs = [];
106
+ const addedIds = new Set();
107
+ editorConfig.channels.forEach(channel => {
108
+ channel.segments.forEach(segment => {
109
+ if ('inputRef' in segment && segment.inputRef && !addedIds.has(segment.inputRef)) {
110
+ addedIds.add(segment.inputRef);
111
+ const inputType = segment.type === 'video' ? 'video'
112
+ : segment.type === 'audio' ? 'audio'
113
+ : 'image';
114
+ dynamicInputs.push({
115
+ id: segment.inputRef,
116
+ type: inputType,
117
+ required: true,
118
+ });
119
+ }
120
+ if ('textInputRef' in segment && segment.textInputRef && !addedIds.has(segment.textInputRef)) {
121
+ addedIds.add(segment.textInputRef);
122
+ dynamicInputs.push({
123
+ id: segment.textInputRef,
124
+ type: 'text',
125
+ required: true,
126
+ });
127
+ }
128
+ });
129
+ });
130
+ return dynamicInputs;
131
+ }
132
+ // Workflow nodes: generate inputs from embedded workflow's variable nodes
133
+ if (node.type === 'compose-workflow') {
134
+ const variableNodes = node.config?.workflowVariableNodes;
135
+ if (!variableNodes || variableNodes.length === 0)
136
+ return baseInputs;
137
+ const passThrough = (node.config?.workflowPassThrough ?? {});
138
+ return variableNodes
139
+ .filter(v => {
140
+ const varName = v.name ?? v.id;
141
+ return !passThrough[varName];
142
+ })
143
+ .map(v => ({
144
+ id: v.id,
145
+ type: (v.variableType === 'dropdown' ? 'text' : (v.variableType ?? 'text')),
146
+ required: false,
147
+ }));
148
+ }
149
+ // Output nodes: generate inputs from outputConfig.inputs
150
+ if (node.type === 'output') {
151
+ const outputConfig = node.config?.outputConfig;
152
+ const configInputs = outputConfig?.inputs;
153
+ if (!configInputs || configInputs.length === 0)
154
+ return baseInputs;
155
+ return configInputs.map((input) => ({
156
+ id: input.id,
157
+ type: input.type,
158
+ required: false,
159
+ }));
160
+ }
161
+ // Auto-post nodes: video or slideshow mode
162
+ if (node.type === 'auto-post') {
163
+ const autoPostConfig = node.config?.autoPostConfig;
164
+ const mode = autoPostConfig?.mode ?? 'video';
165
+ const dynamicInputs = [];
166
+ if (mode === 'video') {
167
+ // Video mode: video input required
168
+ dynamicInputs.push({ id: 'video', type: 'video', required: true });
169
+ }
170
+ else {
171
+ // Slideshow mode
172
+ const inputType = autoPostConfig?.inputType ?? 'static';
173
+ if (inputType === 'variable') {
174
+ // Variable mode: single images array port
175
+ dynamicInputs.push({ id: 'images', type: 'object', required: true });
176
+ }
177
+ else {
178
+ // Static mode: individual image inputs
179
+ const imageInputs = autoPostConfig?.inputs ?? [
180
+ { id: 'image1' },
181
+ { id: 'image2' },
182
+ { id: 'image3' },
183
+ ];
184
+ imageInputs.forEach((input) => {
185
+ dynamicInputs.push({ id: input.id, type: 'image', required: true });
186
+ });
187
+ }
188
+ }
189
+ // Add static inputs (same for both modes)
190
+ dynamicInputs.push({ id: 'account', type: 'account', required: true }, { id: 'caption', type: 'text', required: false });
191
+ // Only add social-audio port if isVariable is true
192
+ if (autoPostConfig?.socialAudio?.isVariable) {
193
+ dynamicInputs.push({ id: 'social-audio', type: 'audio', required: false });
194
+ }
195
+ return dynamicInputs;
196
+ }
197
+ // Save-to-media nodes: generate inputs from saveToMediaConfig.inputs
198
+ if (node.type === 'save-to-media') {
199
+ const saveConfig = node.config?.saveToMediaConfig;
200
+ const configInputs = saveConfig?.inputs;
201
+ if (!configInputs || configInputs.length === 0)
202
+ return baseInputs;
203
+ return configInputs.map((input) => ({
204
+ id: input.id,
205
+ type: input.type,
206
+ required: true,
207
+ }));
208
+ }
209
+ // LLM nodes: generate inputs from systemPrompt variables + media refs
210
+ if (node.type === 'llm') {
211
+ const llmConfig = node.config?.llm;
212
+ const dynamicInputs = [];
213
+ // Extract {{variables}} from systemPrompt
214
+ // Variable inputs accept text, object (for JSON data), and boolean types
215
+ const systemPrompt = llmConfig?.systemPrompt ?? '';
216
+ const variables = extractTemplateVariables([systemPrompt]);
217
+ variables.forEach(v => dynamicInputs.push({
218
+ id: v,
219
+ type: ['text', 'object', 'boolean'],
220
+ required: true,
221
+ }));
222
+ // Add image input refs
223
+ llmConfig?.imageInputRefs?.forEach((ref) => dynamicInputs.push({ id: ref, type: 'image', required: true }));
224
+ // Add video input refs
225
+ llmConfig?.videoInputRefs?.forEach((ref) => dynamicInputs.push({ id: ref, type: 'video', required: true }));
226
+ return dynamicInputs.length > 0 ? dynamicInputs : baseInputs;
227
+ }
228
+ // For-each nodes: array input + passthrough inputs
229
+ if (node.type === 'for-each') {
230
+ const forEachConfig = node.config?.forEachConfig;
231
+ const inputPorts = forEachConfig?.inputPorts ?? [];
232
+ return [
233
+ { id: 'array', type: 'object', required: true },
234
+ ...inputPorts.map((port) => ({
235
+ id: port.id,
236
+ type: port.type,
237
+ required: false,
238
+ })),
239
+ ];
240
+ }
241
+ // If nodes: boolean inputs + passthrough inputs from resolvedPorts
242
+ if (node.type === 'if') {
243
+ const resolvedPorts = node.config?.resolvedPorts;
244
+ if (resolvedPorts?.inputs && resolvedPorts.inputs.length > 0) {
245
+ return resolvedPorts.inputs.map(port => ({
246
+ id: port.id,
247
+ type: port.type,
248
+ required: port.required,
249
+ }));
250
+ }
251
+ return baseInputs;
252
+ }
253
+ // Transcript nodes: media input
254
+ if (node.type === 'transcript') {
255
+ return [{ id: 'media', type: ['video', 'audio'], required: true }];
256
+ }
257
+ // Video import nodes: url input
258
+ if (node.type === 'video-import') {
259
+ return [{ id: 'url', type: 'text', required: true }];
260
+ }
261
+ // Image Generation nodes: prompt + optional image for edit models
262
+ if (node.type === 'generate-image') {
263
+ const imageGenConfig = node.config?.imageGeneration;
264
+ const model = imageGenConfig?.model ?? '';
265
+ const isEditModel = model.includes('/edit');
266
+ const dynamicInputs = [
267
+ { id: 'prompt', type: 'text', required: true },
268
+ ];
269
+ if (isEditModel) {
270
+ dynamicInputs.push({ id: 'image', type: 'image', required: true });
271
+ }
272
+ return dynamicInputs;
273
+ }
274
+ // Custom Model nodes: generate inputs from inputParams
275
+ if (node.type === 'custom-model') {
276
+ const customModelConfig = node.config?.customModelConfig;
277
+ const inputParams = customModelConfig?.inputParams ?? [];
278
+ const dynamicInputs = [];
279
+ inputParams.forEach((param) => {
280
+ if (param.name === 'prompt' || param.type === 'image' || param.type === 'video' || param.type === 'audio') {
281
+ dynamicInputs.push({
282
+ id: param.name,
283
+ type: (param.type === 'image' || param.type === 'video' || param.type === 'audio'
284
+ ? param.type
285
+ : 'text'),
286
+ required: param.required,
287
+ });
288
+ }
289
+ });
290
+ return dynamicInputs;
291
+ }
292
+ // Deduplicate nodes: video input
293
+ if (node.type === 'deduplicate') {
294
+ return [{ id: 'video', type: 'video', required: true }];
295
+ }
296
+ return baseInputs;
297
+ }
298
+ /**
299
+ * Compute the output ports for a node based on its type and config
300
+ */
301
+ function computeOutputPorts({ node }) {
302
+ const nodeConfigs = (0, automations_1.getAllNodes)();
303
+ const nodeConfig = nodeConfigs.find(n => n.type === node.type);
304
+ const baseOutputs = nodeConfig?.outputs ?? [];
305
+ // Sub-agent: outputs from resolvedPorts or workflow output schema
306
+ if (node.type === 'compose-workflow') {
307
+ // Prefer resolvedPorts if available (has correct array types computed)
308
+ const resolvedPorts = node.config?.resolvedPorts;
309
+ if (resolvedPorts?.outputs && resolvedPorts.outputs.length > 0) {
310
+ return resolvedPorts.outputs.map(port => ({
311
+ id: port.id,
312
+ type: port.type,
313
+ required: port.required,
314
+ }));
315
+ }
316
+ // Fallback to workflowOutputSchema with isArray handling
317
+ const outputSchema = node.config?.workflowOutputSchema;
318
+ if (!outputSchema || Object.keys(outputSchema).length === 0)
319
+ return baseOutputs;
320
+ return Object.entries(outputSchema).map(([id, schema]) => ({
321
+ id,
322
+ type: (schema.isArray ? `${schema.type}[]` : schema.type),
323
+ required: true,
324
+ }));
325
+ }
326
+ // LLM nodes: outputs from outputFields
327
+ // Array types output 'object', others output 'text'
328
+ if (node.type === 'llm') {
329
+ const llmConfig = node.config?.llm;
330
+ const outputFields = llmConfig?.outputFields ?? [{ name: 'output', type: 'string' }];
331
+ return outputFields.map((f) => ({
332
+ id: f.name,
333
+ type: (f.type === 'array' ? 'object' : 'text'),
334
+ required: true,
335
+ }));
336
+ }
337
+ // For-each nodes: outputs based on forEachConfig
338
+ if (node.type === 'for-each') {
339
+ // Prefer resolvedPorts if available (computed by modal with accurate item types)
340
+ const resolvedPorts = node.config?.resolvedPorts;
341
+ if (resolvedPorts?.outputs && resolvedPorts.outputs.length > 0) {
342
+ return resolvedPorts.outputs.map(port => ({
343
+ id: port.id,
344
+ type: port.type,
345
+ required: port.required,
346
+ }));
347
+ }
348
+ // Fallback to manual computation (for backwards compatibility)
349
+ const forEachConfig = node.config?.forEachConfig;
350
+ const outputProperties = forEachConfig?.outputProperties ?? [];
351
+ const inputPorts = forEachConfig?.inputPorts ?? [];
352
+ const exposeItem = forEachConfig?.exposeItem ?? false;
353
+ const exposeIndex = forEachConfig?.exposeIndex ?? false;
354
+ const outputs = [];
355
+ if (exposeItem) {
356
+ outputs.push({ id: 'item', type: 'object', required: true });
357
+ }
358
+ if (exposeIndex) {
359
+ outputs.push({ id: 'index', type: 'number', required: true });
360
+ }
361
+ for (const prop of outputProperties) {
362
+ outputs.push({
363
+ id: prop.portId,
364
+ type: prop.type,
365
+ required: true,
366
+ });
367
+ }
368
+ for (const port of inputPorts) {
369
+ outputs.push({
370
+ id: port.id,
371
+ type: port.type,
372
+ required: true,
373
+ });
374
+ }
375
+ return outputs;
376
+ }
377
+ // If nodes: outputs from resolvedPorts
378
+ if (node.type === 'if') {
379
+ const resolvedPorts = node.config?.resolvedPorts;
380
+ if (resolvedPorts?.outputs && resolvedPorts.outputs.length > 0) {
381
+ return resolvedPorts.outputs.map(port => ({
382
+ id: port.id,
383
+ type: port.type,
384
+ required: port.required,
385
+ }));
386
+ }
387
+ return baseOutputs;
388
+ }
389
+ // Transcript nodes: text and segments outputs
390
+ if (node.type === 'transcript') {
391
+ return [
392
+ { id: 'text', type: 'text', required: true },
393
+ { id: 'segments', type: 'object', required: true },
394
+ ];
395
+ }
396
+ // Video import nodes: video output
397
+ if (node.type === 'video-import') {
398
+ return [{ id: 'output', type: 'video', required: true }];
399
+ }
400
+ // Deduplicate nodes: video output
401
+ if (node.type === 'deduplicate') {
402
+ return [{ id: 'output', type: 'video', required: true }];
403
+ }
404
+ // Manual Trigger nodes: outputs from resolvedPorts or config
405
+ if (node.type === 'manual-trigger') {
406
+ // Prefer resolvedPorts if available (computed by modal with correct array types)
407
+ const resolvedPorts = node.config?.resolvedPorts;
408
+ if (resolvedPorts?.outputs && resolvedPorts.outputs.length > 0) {
409
+ return resolvedPorts.outputs.map(port => ({
410
+ id: port.id,
411
+ type: port.type,
412
+ required: port.required,
413
+ }));
414
+ }
415
+ // Fallback to config (handle isArray flag)
416
+ const triggerConfig = node.config?.manualTriggerConfig;
417
+ const configOutputs = triggerConfig?.outputs ?? [];
418
+ if (configOutputs.length === 0)
419
+ return baseOutputs;
420
+ return configOutputs.map((output) => ({
421
+ id: output.id,
422
+ type: (output.isArray ? `${output.type}[]` : output.type),
423
+ required: true,
424
+ }));
425
+ }
426
+ // Recurrence nodes: add account output if enabled
427
+ if (node.type === 'recurrence') {
428
+ const recurrenceConfig = node.config?.recurrenceConfig;
429
+ const collectionInput = recurrenceConfig?.collectionInput;
430
+ if (collectionInput?.enabled && (collectionInput.accountIds?.length ?? 0) > 0) {
431
+ return [
432
+ ...baseOutputs,
433
+ { id: 'account', type: 'account', required: true },
434
+ ];
435
+ }
436
+ }
437
+ // Media nodes: outputs based on outputs array
438
+ if (node.type === 'media') {
439
+ const mediaConfig = node.config?.mediaConfig;
440
+ const configOutputs = mediaConfig?.outputs ?? [];
441
+ // Only include outputs with actual selections
442
+ const activeOutputs = configOutputs.filter(o => o.selectedMediaIds.length > 0);
443
+ if (activeOutputs.length === 0)
444
+ return baseOutputs;
445
+ return activeOutputs.map(o => ({
446
+ id: o.id,
447
+ type: (o.isArray ? `${o.type}[]` : o.type),
448
+ required: true,
449
+ }));
450
+ }
451
+ // Account nodes
452
+ if (node.type === 'account') {
453
+ return [{ id: 'account', type: 'account', required: true }];
454
+ }
455
+ // Custom Model nodes: single output based on outputType
456
+ if (node.type === 'custom-model') {
457
+ const customModelConfig = node.config?.customModelConfig;
458
+ const outputType = customModelConfig?.outputType ?? 'image';
459
+ return [{ id: 'output', type: outputType, required: true }];
460
+ }
461
+ return baseOutputs;
462
+ }
463
+ /**
464
+ * Check if an input port is required for a given node.
465
+ */
466
+ function isInputPortRequired({ node, portId, }) {
467
+ const ports = computeInputPorts({ node });
468
+ const port = ports.find(p => p.id === portId);
469
+ return port?.required ?? true; // Default to required for safety
470
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ugcinc",
3
- "version": "3.85.1",
3
+ "version": "3.87.0",
4
4
  "description": "TypeScript/JavaScript client for the UGC Inc API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",