@sogni-ai/sogni-creative-agent-skill 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -10
- package/SKILL.md +73 -17
- package/generated/creative-agent-runtime.mjs +4076 -148
- package/llm.txt +15 -5
- package/openclaw.plugin.json +25 -2
- package/package.json +6 -2
- package/scripts/check-creative-agent-source.mjs +104 -0
- package/sogni-agent.mjs +574 -75
- package/ssrf-guard.mjs +2 -1
- package/version.mjs +1 -1
package/sogni-agent.mjs
CHANGED
|
@@ -21,6 +21,9 @@ import {
|
|
|
21
21
|
SEEDANCE_V2V_REFERENCE_MAX_DURATION_SECONDS,
|
|
22
22
|
VIDEO_WORKFLOW_DEFAULT_MODELS,
|
|
23
23
|
buildStoryboardVideoHostedToolSequenceInput,
|
|
24
|
+
classifySkillError,
|
|
25
|
+
compilePublicSkillToolSurface,
|
|
26
|
+
createPublicSkillDefaultContractRuntime,
|
|
24
27
|
detectReferenceAudioFormat,
|
|
25
28
|
dimensionsForAspectRatio,
|
|
26
29
|
dimensionsWithShortSide,
|
|
@@ -33,9 +36,11 @@ import {
|
|
|
33
36
|
isSeedanceModelSelection,
|
|
34
37
|
normalizeVideoWorkflow,
|
|
35
38
|
planCliVideoBrain,
|
|
39
|
+
PUBLIC_SKILL_DEFAULT_TOOL_DEFINITIONS,
|
|
36
40
|
resolveVideoControlNetStrength,
|
|
37
41
|
resolveVideoModelAlias,
|
|
38
42
|
resolveVideoSteps,
|
|
43
|
+
sanitizeMessagesForLlm,
|
|
39
44
|
sanitizeBatchPrompt,
|
|
40
45
|
selectDefaultVideoModel,
|
|
41
46
|
shouldTrimSeedanceV2VSourceVideo,
|
|
@@ -86,10 +91,12 @@ const DEFAULT_MEMORIES_PATH = join(homedir(), '.config', 'sogni', 'memories.json
|
|
|
86
91
|
const DEFAULT_PERSONALITY_PATH = join(homedir(), '.config', 'sogni', 'personality.txt');
|
|
87
92
|
const DEFAULT_PERSONAS_DIR = join(homedir(), '.config', 'sogni', 'personas');
|
|
88
93
|
const DEFAULT_PERSONAS_INDEX_PATH = join(homedir(), '.config', 'sogni', 'personas', 'index.json');
|
|
94
|
+
const DEFAULT_API_MEDIA_REFERENCE_MAX_BYTES = 100 * 1024 * 1024;
|
|
89
95
|
const DEFAULT_API_BASE_URL = 'https://api.sogni.ai';
|
|
90
96
|
const DEFAULT_SAFE_API_HOSTS = Object.freeze(['api.sogni.ai']);
|
|
91
97
|
const LOOPBACK_API_HOSTS = Object.freeze(['localhost', '127.0.0.1', '::1']);
|
|
92
98
|
const DEFAULT_LLM_MODEL = 'qwen3.6-35b-a3b-gguf-iq4xs';
|
|
99
|
+
const VALID_API_TASK_PROFILES = new Set(['general', 'coding', 'reasoning']);
|
|
93
100
|
const SOGNI_APP_SOURCE = 'sogni-creative-agent-skill';
|
|
94
101
|
const OPENCLAW_CONFIG_PATH = getEnv('OPENCLAW_CONFIG_PATH') || DEFAULT_OPENCLAW_CONFIG_PATH;
|
|
95
102
|
const IS_OPENCLAW_INVOCATION = Boolean(getEnv('OPENCLAW_PLUGIN_CONFIG'));
|
|
@@ -160,9 +167,13 @@ function isPathWithinBase(basePath, targetPath) {
|
|
|
160
167
|
}
|
|
161
168
|
|
|
162
169
|
function buildCliErrorPayload({ message, code, details, hint, prompt }) {
|
|
170
|
+
const classified = classifySkillError({ message, code });
|
|
163
171
|
const payload = {
|
|
164
172
|
success: false,
|
|
165
173
|
error: message || 'Unknown error',
|
|
174
|
+
errorType: classified.error_type,
|
|
175
|
+
errorCategory: classified.category,
|
|
176
|
+
retryable: classified.retryable,
|
|
166
177
|
prompt: prompt ?? null
|
|
167
178
|
};
|
|
168
179
|
if (code) payload.errorCode = code;
|
|
@@ -175,6 +186,14 @@ function buildCliErrorPayload({ message, code, details, hint, prompt }) {
|
|
|
175
186
|
return payload;
|
|
176
187
|
}
|
|
177
188
|
|
|
189
|
+
function addCanonicalErrorFields(payload, error) {
|
|
190
|
+
const classified = classifySkillError(error);
|
|
191
|
+
payload.errorType = classified.error_type;
|
|
192
|
+
payload.errorCategory = classified.category;
|
|
193
|
+
payload.retryable = classified.retryable;
|
|
194
|
+
return payload;
|
|
195
|
+
}
|
|
196
|
+
|
|
178
197
|
function fatalCliError(message, opts = {}) {
|
|
179
198
|
let prompt = opts.prompt;
|
|
180
199
|
if (prompt === undefined) {
|
|
@@ -314,8 +333,9 @@ function normalizeSeedStrategy(value) {
|
|
|
314
333
|
|
|
315
334
|
function normalizeApiToolMode(value) {
|
|
316
335
|
const normalized = String(value || 'creative-agent').toLowerCase();
|
|
317
|
-
if (normalized === 'creative-agent'
|
|
318
|
-
if (normalized === '
|
|
336
|
+
if (normalized === 'creative-agent') return 'creative-agent';
|
|
337
|
+
if (normalized === 'creative-tools') return 'creative-tools';
|
|
338
|
+
if (normalized === 'true') return true;
|
|
319
339
|
if (normalized === 'none' || normalized === 'false') return false;
|
|
320
340
|
return null;
|
|
321
341
|
}
|
|
@@ -324,6 +344,7 @@ function normalizeApiWorkflowKind(value) {
|
|
|
324
344
|
const normalized = String(value || '').toLowerCase().replace(/-/g, '_');
|
|
325
345
|
if (normalized === 'image_to_video' || normalized === 'i2v') return 'image_to_video';
|
|
326
346
|
if (normalized === 'hosted_tool_sequence' || normalized === 'tool_sequence') return 'hosted_tool_sequence';
|
|
347
|
+
if (normalized === 'creative_plan' || normalized === 'plan') return 'creative_plan';
|
|
327
348
|
if (normalized === 'storyboard_video' || normalized === 'storyboard_to_video' || normalized === 'gpt_image_2_seedance' || normalized === 'gpt_image_seedance') {
|
|
328
349
|
return 'storyboard_video';
|
|
329
350
|
}
|
|
@@ -369,7 +390,8 @@ async function buildSafeApiUrl(path) {
|
|
|
369
390
|
throw err;
|
|
370
391
|
}
|
|
371
392
|
|
|
372
|
-
|
|
393
|
+
const hasEmbeddedCredentials = Boolean(parsed['user' + 'name'] || parsed['pass' + 'word']);
|
|
394
|
+
if (hasEmbeddedCredentials) {
|
|
373
395
|
const err = new Error('Sogni API base URL must not contain credentials.');
|
|
374
396
|
err.code = 'UNSAFE_API_BASE_URL';
|
|
375
397
|
throw err;
|
|
@@ -1114,16 +1136,27 @@ const options = {
|
|
|
1114
1136
|
apiChat: false,
|
|
1115
1137
|
apiBaseUrl: null,
|
|
1116
1138
|
llmModel: DEFAULT_LLM_MODEL,
|
|
1139
|
+
apiTaskProfile: null,
|
|
1140
|
+
apiMaxTokens: null,
|
|
1141
|
+
apiThinking: null,
|
|
1117
1142
|
apiTools: 'creative-agent',
|
|
1118
1143
|
apiToolExecution: true,
|
|
1119
1144
|
apiSystemPrompt: null,
|
|
1120
|
-
|
|
1121
|
-
|
|
1145
|
+
apiModelAction: null, // list|get
|
|
1146
|
+
apiModelId: null,
|
|
1147
|
+
apiReplayAction: null, // list|get|ingest
|
|
1148
|
+
apiReplayId: null,
|
|
1149
|
+
apiReplayInput: null,
|
|
1150
|
+
apiReplayLimit: 50,
|
|
1151
|
+
apiWorkflowAction: null, // start|list|get|events|stream|cancel|resume
|
|
1152
|
+
apiWorkflowKind: null, // image_to_video|hosted_tool_sequence|creative_plan|storyboard_video
|
|
1122
1153
|
apiWorkflowInput: null,
|
|
1123
1154
|
apiWorkflowTitle: null,
|
|
1124
1155
|
apiWorkflowIdempotencyKey: null,
|
|
1125
1156
|
apiWorkflowId: null,
|
|
1126
1157
|
apiWorkflowWatch: false,
|
|
1158
|
+
apiWorkflowMaxCost: null,
|
|
1159
|
+
apiWorkflowConfirmCost: null,
|
|
1127
1160
|
apiVideoPrompt: null,
|
|
1128
1161
|
apiNegativePrompt: null,
|
|
1129
1162
|
apiGenerateAudio: null,
|
|
@@ -1197,12 +1230,17 @@ const cliSet = {
|
|
|
1197
1230
|
lastFrameStrength: false,
|
|
1198
1231
|
apiBaseUrl: false,
|
|
1199
1232
|
llmModel: false,
|
|
1233
|
+
apiTaskProfile: false,
|
|
1234
|
+
apiMaxTokens: false,
|
|
1235
|
+
apiThinking: false,
|
|
1200
1236
|
apiTools: false,
|
|
1201
1237
|
apiSystemPrompt: false,
|
|
1202
1238
|
apiWorkflowKind: false,
|
|
1203
1239
|
apiWorkflowInput: false,
|
|
1204
1240
|
apiWorkflowTitle: false,
|
|
1205
1241
|
apiWorkflowIdempotencyKey: false,
|
|
1242
|
+
apiWorkflowMaxCost: false,
|
|
1243
|
+
apiWorkflowConfirmCost: false,
|
|
1206
1244
|
apiVideoPrompt: false,
|
|
1207
1245
|
apiNegativePrompt: false,
|
|
1208
1246
|
apiGenerateAudio: false,
|
|
@@ -1609,6 +1647,22 @@ for (let i = 0; i < args.length; i++) {
|
|
|
1609
1647
|
i++;
|
|
1610
1648
|
options.llmModel = raw;
|
|
1611
1649
|
cliSet.llmModel = true;
|
|
1650
|
+
} else if (arg === '--task-profile') {
|
|
1651
|
+
const raw = requireFlagValue(args, i, arg);
|
|
1652
|
+
i++;
|
|
1653
|
+
options.apiTaskProfile = raw;
|
|
1654
|
+
cliSet.apiTaskProfile = true;
|
|
1655
|
+
} else if (arg === '--max-tokens' || arg === '--max-completion-tokens') {
|
|
1656
|
+
const raw = requireFlagValue(args, i, arg);
|
|
1657
|
+
i++;
|
|
1658
|
+
options.apiMaxTokens = parsePositiveIntegerValue(raw, arg);
|
|
1659
|
+
cliSet.apiMaxTokens = true;
|
|
1660
|
+
} else if (arg === '--thinking') {
|
|
1661
|
+
options.apiThinking = true;
|
|
1662
|
+
cliSet.apiThinking = true;
|
|
1663
|
+
} else if (arg === '--no-thinking') {
|
|
1664
|
+
options.apiThinking = false;
|
|
1665
|
+
cliSet.apiThinking = true;
|
|
1612
1666
|
} else if (arg === '--api-tools') {
|
|
1613
1667
|
const raw = requireFlagValue(args, i, arg);
|
|
1614
1668
|
i++;
|
|
@@ -1621,6 +1675,30 @@ for (let i = 0; i < args.length; i++) {
|
|
|
1621
1675
|
i++;
|
|
1622
1676
|
options.apiSystemPrompt = raw;
|
|
1623
1677
|
cliSet.apiSystemPrompt = true;
|
|
1678
|
+
} else if (arg === '--list-api-models' || arg === '--api-models') {
|
|
1679
|
+
options.apiModelAction = 'list';
|
|
1680
|
+
} else if (arg === '--get-api-model' || arg === '--api-model') {
|
|
1681
|
+
const raw = requireFlagValue(args, i, arg);
|
|
1682
|
+
i++;
|
|
1683
|
+
options.apiModelAction = 'get';
|
|
1684
|
+
options.apiModelId = raw;
|
|
1685
|
+
} else if (arg === '--list-replays' || arg === '--list-replay-records') {
|
|
1686
|
+
options.apiReplayAction = 'list';
|
|
1687
|
+
const next = args[i + 1];
|
|
1688
|
+
if (next && !next.startsWith('-')) {
|
|
1689
|
+
i++;
|
|
1690
|
+
options.apiReplayLimit = parsePositiveIntegerValue(next, arg);
|
|
1691
|
+
}
|
|
1692
|
+
} else if (arg === '--get-replay' || arg === '--get-replay-record') {
|
|
1693
|
+
const raw = requireFlagValue(args, i, arg);
|
|
1694
|
+
i++;
|
|
1695
|
+
options.apiReplayAction = 'get';
|
|
1696
|
+
options.apiReplayId = raw;
|
|
1697
|
+
} else if (arg === '--ingest-replay' || arg === '--ingest-replay-record') {
|
|
1698
|
+
const raw = requireFlagValue(args, i, arg);
|
|
1699
|
+
i++;
|
|
1700
|
+
options.apiReplayAction = 'ingest';
|
|
1701
|
+
options.apiReplayInput = raw;
|
|
1624
1702
|
} else if (arg === '--api-workflow' || arg === '--creative-workflow') {
|
|
1625
1703
|
const raw = requireFlagValue(args, i, arg);
|
|
1626
1704
|
i++;
|
|
@@ -1642,6 +1720,17 @@ for (let i = 0; i < args.length; i++) {
|
|
|
1642
1720
|
i++;
|
|
1643
1721
|
options.apiWorkflowIdempotencyKey = raw;
|
|
1644
1722
|
cliSet.apiWorkflowIdempotencyKey = true;
|
|
1723
|
+
} else if (arg === '--workflow-max-cost' || arg === '--max-workflow-cost') {
|
|
1724
|
+
const raw = requireFlagValue(args, i, arg);
|
|
1725
|
+
i++;
|
|
1726
|
+
options.apiWorkflowMaxCost = parseNonNegativeNumberValue(raw, arg);
|
|
1727
|
+
cliSet.apiWorkflowMaxCost = true;
|
|
1728
|
+
} else if (arg === '--confirm-cost') {
|
|
1729
|
+
options.apiWorkflowConfirmCost = true;
|
|
1730
|
+
cliSet.apiWorkflowConfirmCost = true;
|
|
1731
|
+
} else if (arg === '--no-confirm-cost') {
|
|
1732
|
+
options.apiWorkflowConfirmCost = false;
|
|
1733
|
+
cliSet.apiWorkflowConfirmCost = true;
|
|
1645
1734
|
} else if (arg === '--storyboard-frames') {
|
|
1646
1735
|
const raw = requireFlagValue(args, i, arg);
|
|
1647
1736
|
i++;
|
|
@@ -1693,6 +1782,11 @@ for (let i = 0; i < args.length; i++) {
|
|
|
1693
1782
|
i++;
|
|
1694
1783
|
options.apiWorkflowAction = 'cancel';
|
|
1695
1784
|
options.apiWorkflowId = raw;
|
|
1785
|
+
} else if (arg === '--resume-workflow') {
|
|
1786
|
+
const raw = requireFlagValue(args, i, arg);
|
|
1787
|
+
i++;
|
|
1788
|
+
options.apiWorkflowAction = 'resume';
|
|
1789
|
+
options.apiWorkflowId = raw;
|
|
1696
1790
|
// --- Memory commands ---
|
|
1697
1791
|
} else if (arg === '--memory-set') {
|
|
1698
1792
|
options.memoryAction = 'set';
|
|
@@ -1868,15 +1962,25 @@ Video Options:
|
|
|
1868
1962
|
--last-image Use last generated image as reference
|
|
1869
1963
|
|
|
1870
1964
|
Hosted API Modes:
|
|
1871
|
-
--api-chat Use /v1/chat/completions with
|
|
1872
|
-
--api-tools <mode> creative-agent|
|
|
1965
|
+
--api-chat Use /v1/chat/completions with Sogni creative-agent tools
|
|
1966
|
+
--api-tools <mode> creative-agent|creative-tools|none (default: creative-agent)
|
|
1873
1967
|
--no-api-tool-execution Ask for tool calls/plans but do not execute Sogni tools
|
|
1874
1968
|
--llm-model <id> LLM model for --api-chat (default: ${DEFAULT_LLM_MODEL})
|
|
1969
|
+
--task-profile <p> LLM task profile for --api-chat: general|coding|reasoning
|
|
1970
|
+
--max-tokens <num> Max chat completion tokens for --api-chat and storyboard planning
|
|
1971
|
+
--thinking, --no-thinking Toggle chat_template_kwargs.enable_thinking
|
|
1875
1972
|
--system <text> System prompt for --api-chat
|
|
1876
|
-
--api-
|
|
1877
|
-
--
|
|
1878
|
-
--
|
|
1973
|
+
--list-api-models List Sogni Intelligence LLM models from /v1/models
|
|
1974
|
+
--get-api-model <id> Fetch one Sogni Intelligence model descriptor
|
|
1975
|
+
--list-replays [n] List recent /v1/replay/records (default: 50)
|
|
1976
|
+
--get-replay <id> Fetch one replay RunRecord
|
|
1977
|
+
--ingest-replay <json|path|@path> POST a RunRecord to /v1/replay/records
|
|
1978
|
+
--api-workflow <kind> Start /v1/creative-agent/workflows: image-to-video|hosted-tool-sequence|creative-plan|storyboard-video
|
|
1979
|
+
--workflow-input <json|path|@path> JSON input for hosted-tool-sequence/creative-plan/custom image-to-video/storyboard-video
|
|
1980
|
+
--workflow-title <text> Title for hosted-tool-sequence, creative-plan, or storyboard-video workflow input
|
|
1879
1981
|
--workflow-idempotency-key <key> Reuse safely when retrying a workflow start request
|
|
1982
|
+
--workflow-max-cost <n> Reject the workflow if estimated capacity units exceed n
|
|
1983
|
+
--confirm-cost, --no-confirm-cost Forward explicit workflow cost confirmation
|
|
1880
1984
|
--storyboard-frames <n> Frame/beat count for --api-workflow storyboard-video
|
|
1881
1985
|
--video-prompt <text> Motion prompt for --api-workflow image-to-video
|
|
1882
1986
|
--negative-prompt <text> Negative prompt for --api-workflow image-to-video
|
|
@@ -1888,6 +1992,7 @@ Hosted API Modes:
|
|
|
1888
1992
|
--workflow-events <id> Fetch workflow event history
|
|
1889
1993
|
--stream-workflow <id> Stream workflow events over SSE
|
|
1890
1994
|
--cancel-workflow <id> Cancel a running workflow
|
|
1995
|
+
--resume-workflow <id> Resume a failed, partial, waiting, or running durable workflow
|
|
1891
1996
|
--api-base-url <url> Sogni API base URL (default: ${DEFAULT_API_BASE_URL})
|
|
1892
1997
|
|
|
1893
1998
|
General:
|
|
@@ -1991,6 +2096,7 @@ Examples:
|
|
|
1991
2096
|
sogni-agent --video --reference-audio-identity voice.webm 'NARRATOR: "This is my voice."'
|
|
1992
2097
|
sogni-agent --api-chat "Create a 4-shot product video concept for a red sneaker"
|
|
1993
2098
|
sogni-agent --api-workflow image-to-video --video-prompt "slow push-in as it comes alive" "a graphite robot sketch"
|
|
2099
|
+
sogni-agent --api-workflow creative-plan --workflow-input @plan.json
|
|
1994
2100
|
sogni-agent --api-workflow storyboard-video --storyboard-frames 6 "Create a 12s 9:16 bakery launch video with GPT Image 2 and Seedance"
|
|
1995
2101
|
sogni-agent --video -m ltx23-22b-fp8_t2v_distilled --duration 20 "A wide cinematic aerial shot opens over steep tropical cliffs at golden hour, warm sunlight grazing the rock faces while sea mist drifts above the water below. Palm trees bend gently along the ridge as waves roll against the shoreline, leaving bright bands of foam across the dark stone. The camera glides forward in one continuous pass, revealing more of the coastline as sunlight flickers across wet surfaces and distant birds wheel through the haze. The scene holds a calm, upscale travel-film mood with smooth stabilized motion and crisp environmental detail."
|
|
1996
2102
|
sogni-agent --video --ref subject.jpg --ref-video motion.mp4 --workflow animate-move "transfer motion"
|
|
@@ -2050,9 +2156,36 @@ if (openclawConfig) {
|
|
|
2050
2156
|
if (!cliSet.llmModel && openclawConfig.defaultLlmModel) {
|
|
2051
2157
|
options.llmModel = openclawConfig.defaultLlmModel;
|
|
2052
2158
|
}
|
|
2159
|
+
if (!cliSet.apiTaskProfile && openclawConfig.defaultTaskProfile) {
|
|
2160
|
+
options.apiTaskProfile = openclawConfig.defaultTaskProfile;
|
|
2161
|
+
}
|
|
2162
|
+
if (!cliSet.apiMaxTokens && Number.isSafeInteger(openclawConfig.defaultApiMaxTokens)) {
|
|
2163
|
+
if (openclawConfig.defaultApiMaxTokens < 1) {
|
|
2164
|
+
fatalCliError('OpenClaw config defaultApiMaxTokens must be a positive integer.', {
|
|
2165
|
+
code: 'INVALID_CONFIG',
|
|
2166
|
+
details: { field: 'defaultApiMaxTokens', value: openclawConfig.defaultApiMaxTokens }
|
|
2167
|
+
});
|
|
2168
|
+
}
|
|
2169
|
+
options.apiMaxTokens = openclawConfig.defaultApiMaxTokens;
|
|
2170
|
+
}
|
|
2171
|
+
if (!cliSet.apiThinking && typeof openclawConfig.defaultApiThinking === 'boolean') {
|
|
2172
|
+
options.apiThinking = openclawConfig.defaultApiThinking;
|
|
2173
|
+
}
|
|
2053
2174
|
if (!cliSet.apiTools && openclawConfig.defaultApiToolMode) {
|
|
2054
2175
|
options.apiTools = openclawConfig.defaultApiToolMode;
|
|
2055
2176
|
}
|
|
2177
|
+
if (!cliSet.apiWorkflowMaxCost && isNumber(openclawConfig.defaultWorkflowMaxCost)) {
|
|
2178
|
+
if (openclawConfig.defaultWorkflowMaxCost < 0) {
|
|
2179
|
+
fatalCliError('OpenClaw config defaultWorkflowMaxCost must be a non-negative number.', {
|
|
2180
|
+
code: 'INVALID_CONFIG',
|
|
2181
|
+
details: { field: 'defaultWorkflowMaxCost', value: openclawConfig.defaultWorkflowMaxCost }
|
|
2182
|
+
});
|
|
2183
|
+
}
|
|
2184
|
+
options.apiWorkflowMaxCost = openclawConfig.defaultWorkflowMaxCost;
|
|
2185
|
+
}
|
|
2186
|
+
if (!cliSet.apiWorkflowConfirmCost && typeof openclawConfig.defaultWorkflowConfirmCost === 'boolean') {
|
|
2187
|
+
options.apiWorkflowConfirmCost = openclawConfig.defaultWorkflowConfirmCost;
|
|
2188
|
+
}
|
|
2056
2189
|
if (!cliSet.seedStrategy && openclawConfig.seedStrategy) {
|
|
2057
2190
|
options.seedStrategy = openclawConfig.seedStrategy;
|
|
2058
2191
|
}
|
|
@@ -2096,9 +2229,20 @@ if (options.tokenType) {
|
|
|
2096
2229
|
options.tokenType = token;
|
|
2097
2230
|
}
|
|
2098
2231
|
|
|
2232
|
+
if (options.apiTaskProfile) {
|
|
2233
|
+
const profile = String(options.apiTaskProfile).trim().toLowerCase();
|
|
2234
|
+
if (!VALID_API_TASK_PROFILES.has(profile)) {
|
|
2235
|
+
fatalCliError('--task-profile must be "general", "coding", or "reasoning".', {
|
|
2236
|
+
code: 'INVALID_ARGUMENT',
|
|
2237
|
+
details: { flag: '--task-profile', value: options.apiTaskProfile }
|
|
2238
|
+
});
|
|
2239
|
+
}
|
|
2240
|
+
options.apiTaskProfile = profile;
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2099
2243
|
const normalizedApiToolMode = normalizeApiToolMode(options.apiTools);
|
|
2100
2244
|
if (normalizedApiToolMode === null) {
|
|
2101
|
-
fatalCliError('--api-tools must be "creative-agent", "
|
|
2245
|
+
fatalCliError('--api-tools must be "creative-agent", "creative-tools", or "none".', {
|
|
2102
2246
|
code: 'INVALID_ARGUMENT',
|
|
2103
2247
|
details: { flag: '--api-tools', value: options.apiTools }
|
|
2104
2248
|
});
|
|
@@ -2108,7 +2252,7 @@ options.apiTools = normalizedApiToolMode;
|
|
|
2108
2252
|
if (options.apiWorkflowKind) {
|
|
2109
2253
|
const normalized = normalizeApiWorkflowKind(options.apiWorkflowKind);
|
|
2110
2254
|
if (!normalized) {
|
|
2111
|
-
fatalCliError('--api-workflow must be "image-to-video", "hosted-tool-sequence", or "storyboard-video".', {
|
|
2255
|
+
fatalCliError('--api-workflow must be "image-to-video", "hosted-tool-sequence", "creative-plan", or "storyboard-video".', {
|
|
2112
2256
|
code: 'INVALID_ARGUMENT',
|
|
2113
2257
|
details: { flag: '--api-workflow', value: options.apiWorkflowKind }
|
|
2114
2258
|
});
|
|
@@ -2566,9 +2710,13 @@ if (options.music) {
|
|
|
2566
2710
|
const apiWorkflowUtilityAction = options.apiWorkflowAction && options.apiWorkflowAction !== 'start';
|
|
2567
2711
|
const apiWorkflowStartAction = options.apiWorkflowAction === 'start';
|
|
2568
2712
|
const apiWorkflowStartHasExternalInput = options.apiWorkflowAction === 'start' && options.apiWorkflowInput;
|
|
2713
|
+
const apiModelUtilityAction = Boolean(options.apiModelAction);
|
|
2714
|
+
const apiReplayUtilityAction = Boolean(options.apiReplayAction);
|
|
2569
2715
|
const personaUtilityAction = Boolean(options.personaAction && options.personaAction !== 'generate');
|
|
2570
2716
|
const commandUsesGenerationSeed = !options.apiChat &&
|
|
2571
2717
|
!apiWorkflowUtilityAction &&
|
|
2718
|
+
!apiModelUtilityAction &&
|
|
2719
|
+
!apiReplayUtilityAction &&
|
|
2572
2720
|
!options.estimateVideoCost &&
|
|
2573
2721
|
!options.showBalance &&
|
|
2574
2722
|
!options.showVersion &&
|
|
@@ -2584,40 +2732,22 @@ if (apiWorkflowStartAction && options.apiWorkflowKind === 'image_to_video' && !o
|
|
|
2584
2732
|
if (apiWorkflowStartAction && options.apiWorkflowKind === 'hosted_tool_sequence' && !apiWorkflowStartHasExternalInput) {
|
|
2585
2733
|
fatalCliError('--api-workflow hosted-tool-sequence requires --workflow-input JSON.', { code: 'INVALID_ARGUMENT' });
|
|
2586
2734
|
}
|
|
2735
|
+
if (apiWorkflowStartAction && options.apiWorkflowKind === 'creative_plan' && !apiWorkflowStartHasExternalInput) {
|
|
2736
|
+
fatalCliError('--api-workflow creative-plan requires --workflow-input JSON.', { code: 'INVALID_ARGUMENT' });
|
|
2737
|
+
}
|
|
2587
2738
|
if (apiWorkflowStartAction && options.apiWorkflowKind === 'storyboard_video' && !options.prompt && !apiWorkflowStartHasExternalInput) {
|
|
2588
2739
|
fatalCliError('--api-workflow storyboard-video requires a prompt or --workflow-input JSON.', { code: 'INVALID_ARGUMENT' });
|
|
2589
2740
|
}
|
|
2590
|
-
if (!options.prompt && !options.apiChat && !apiWorkflowUtilityAction && !apiWorkflowStartAction && !options.estimateVideoCost && !options.multiAngle && !options.showBalance && !options.showVersion && !options.extractLastFrame && !options.concatVideos && !options.listMedia && !options.memoryAction && !options.personalityAction && !personaUtilityAction) {
|
|
2741
|
+
if (!options.prompt && !options.apiChat && !apiWorkflowUtilityAction && !apiWorkflowStartAction && !apiModelUtilityAction && !apiReplayUtilityAction && !options.estimateVideoCost && !options.multiAngle && !options.showBalance && !options.showVersion && !options.extractLastFrame && !options.concatVideos && !options.listMedia && !options.memoryAction && !options.personalityAction && !personaUtilityAction) {
|
|
2591
2742
|
fatalCliError('No prompt provided. Use --help for usage.', { code: 'INVALID_ARGUMENT' });
|
|
2592
2743
|
}
|
|
2593
2744
|
|
|
2594
|
-
if (options.apiChat && !options.prompt &&
|
|
2595
|
-
fatalCliError('--api-chat requires a prompt or
|
|
2745
|
+
if (options.apiChat && !options.prompt && getApiModeMediaReferences().length === 0) {
|
|
2746
|
+
fatalCliError('--api-chat requires a prompt or media reference for planning.', { code: 'INVALID_ARGUMENT' });
|
|
2596
2747
|
}
|
|
2597
2748
|
|
|
2598
|
-
const apiMediaRefs = getApiModeMediaReferences();
|
|
2599
|
-
const apiImageRefs = apiMediaRefs.filter(ref => ref.kind === 'image');
|
|
2600
|
-
const apiNonImageRefs = apiMediaRefs.filter(ref => ref.kind !== 'image');
|
|
2601
|
-
if (options.apiChat && apiNonImageRefs.length > 0) {
|
|
2602
|
-
fatalCliError(
|
|
2603
|
-
`--api-chat does not support ${formatApiMediaFlags(apiNonImageRefs)}. Use the direct CLI path for audio/video media workflows.`,
|
|
2604
|
-
{ code: 'UNSUPPORTED_API_MEDIA_REFERENCE' }
|
|
2605
|
-
);
|
|
2606
|
-
}
|
|
2607
|
-
if (options.apiChat && options.apiToolExecution && apiImageRefs.length > 0) {
|
|
2608
|
-
fatalCliError(
|
|
2609
|
-
'--api-chat with server-side tool execution does not currently support image references. Use the direct CLI path for uploaded-media workflows, or pass --no-api-tool-execution for vision-only chat/planning.',
|
|
2610
|
-
{ code: 'UNSUPPORTED_API_UPLOAD_EXECUTION' }
|
|
2611
|
-
);
|
|
2612
|
-
}
|
|
2613
|
-
if (options.apiWorkflowAction && apiMediaRefs.length > 0) {
|
|
2614
|
-
fatalCliError(
|
|
2615
|
-
`Hosted workflow API modes do not accept CLI media reference flags (${formatApiMediaFlags(apiMediaRefs)}). Use --workflow-input JSON for hosted workflow inputs, or use the direct CLI path for local media workflows.`,
|
|
2616
|
-
{ code: 'UNSUPPORTED_API_MEDIA_REFERENCE' }
|
|
2617
|
-
);
|
|
2618
|
-
}
|
|
2619
2749
|
if (options.apiWorkflowAction === 'start' && options.apiWorkflowKind === 'image_to_video' && options.apiWorkflowTitle) {
|
|
2620
|
-
fatalCliError('--workflow-title is currently only supported with --api-workflow hosted-tool-sequence or storyboard-video.', {
|
|
2750
|
+
fatalCliError('--workflow-title is currently only supported with --api-workflow hosted-tool-sequence, creative-plan, or storyboard-video.', {
|
|
2621
2751
|
code: 'INVALID_ARGUMENT',
|
|
2622
2752
|
details: { flag: '--workflow-title', workflow: options.apiWorkflowKind }
|
|
2623
2753
|
});
|
|
@@ -3003,7 +3133,7 @@ function loadCredentials() {
|
|
|
3003
3133
|
|
|
3004
3134
|
const err = new Error('No Sogni API key found.');
|
|
3005
3135
|
err.code = 'MISSING_CREDENTIALS';
|
|
3006
|
-
err.hint = 'Set SOGNI_API_KEY, or configure SOGNI_CREDENTIALS_PATH with SOGNI_API_KEY. You can find your API key by logging into https://dashboard.sogni.ai and
|
|
3136
|
+
err.hint = 'Set SOGNI_API_KEY, or configure SOGNI_CREDENTIALS_PATH with SOGNI_API_KEY. You can find your API key by logging into https://dashboard.sogni.ai and opening the account menu.';
|
|
3007
3137
|
err.details = {
|
|
3008
3138
|
triedEnv: ['SOGNI_API_KEY'],
|
|
3009
3139
|
triedFile: CREDENTIALS_PATH
|
|
@@ -3026,7 +3156,7 @@ function requireApiKeyCredentials(creds, modeLabel) {
|
|
|
3026
3156
|
if (creds?.SOGNI_API_KEY) return creds.SOGNI_API_KEY;
|
|
3027
3157
|
const err = new Error(`${modeLabel} requires SOGNI_API_KEY API-key authentication.`);
|
|
3028
3158
|
err.code = 'MISSING_API_KEY';
|
|
3029
|
-
err.hint = 'Create an API key and set SOGNI_API_KEY;
|
|
3159
|
+
err.hint = 'Create an API key and set SOGNI_API_KEY; this command only supports API-key authentication.';
|
|
3030
3160
|
throw err;
|
|
3031
3161
|
}
|
|
3032
3162
|
|
|
@@ -3083,6 +3213,178 @@ function formatApiMediaFlags(refs) {
|
|
|
3083
3213
|
return [...new Set(refs.map(ref => ref.flag))].join(', ');
|
|
3084
3214
|
}
|
|
3085
3215
|
|
|
3216
|
+
function apiMediaReferenceMaxBytes() {
|
|
3217
|
+
const configured = Number(getEnv('SOGNI_API_MEDIA_REFERENCE_MAX_BYTES') || '');
|
|
3218
|
+
return Number.isFinite(configured) && configured > 0
|
|
3219
|
+
? configured
|
|
3220
|
+
: DEFAULT_API_MEDIA_REFERENCE_MAX_BYTES;
|
|
3221
|
+
}
|
|
3222
|
+
|
|
3223
|
+
function isRemoteApiMediaReference(value) {
|
|
3224
|
+
return /^https?:\/\//i.test(String(value || ''));
|
|
3225
|
+
}
|
|
3226
|
+
|
|
3227
|
+
function isInlineApiMediaReference(value) {
|
|
3228
|
+
return /^data:[^,]+,/i.test(String(value || ''));
|
|
3229
|
+
}
|
|
3230
|
+
|
|
3231
|
+
function mimeTypeForMediaReference(ref) {
|
|
3232
|
+
const value = String(ref.value || '');
|
|
3233
|
+
const clean = value.split('?')[0].toLowerCase();
|
|
3234
|
+
if (ref.kind === 'video') {
|
|
3235
|
+
if (clean.endsWith('.webm')) return 'video/webm';
|
|
3236
|
+
if (clean.endsWith('.m4v')) return 'video/mp4';
|
|
3237
|
+
}
|
|
3238
|
+
if (ref.kind === 'audio' && clean.endsWith('.webm')) return 'audio/webm';
|
|
3239
|
+
return mimeTypeForPath(value, `${ref.kind}/unknown`);
|
|
3240
|
+
}
|
|
3241
|
+
|
|
3242
|
+
function localApiMediaReferenceFile(ref) {
|
|
3243
|
+
const filePath = sanitizePath(String(ref.value || ''), `${ref.flag} media reference`);
|
|
3244
|
+
const stat = statSync(filePath);
|
|
3245
|
+
if (!stat.isFile()) {
|
|
3246
|
+
const err = new Error(`${ref.flag} must point to a file when using local API media references.`);
|
|
3247
|
+
err.code = 'INVALID_MEDIA_REFERENCE';
|
|
3248
|
+
throw err;
|
|
3249
|
+
}
|
|
3250
|
+
const maxBytes = apiMediaReferenceMaxBytes();
|
|
3251
|
+
if (stat.size > maxBytes) {
|
|
3252
|
+
const err = new Error(`${ref.flag} media reference is ${stat.size} bytes, above the ${maxBytes} byte API upload limit.`);
|
|
3253
|
+
err.code = 'MEDIA_REFERENCE_TOO_LARGE';
|
|
3254
|
+
throw err;
|
|
3255
|
+
}
|
|
3256
|
+
const mimeType = mimeTypeForMediaReference(ref);
|
|
3257
|
+
return {
|
|
3258
|
+
filePath,
|
|
3259
|
+
filename: basename(filePath),
|
|
3260
|
+
byteLength: stat.size,
|
|
3261
|
+
mimeType,
|
|
3262
|
+
};
|
|
3263
|
+
}
|
|
3264
|
+
|
|
3265
|
+
function apiMediaReferenceUploadType(ref, index) {
|
|
3266
|
+
if (ref.kind === 'audio') return 'referenceAudio';
|
|
3267
|
+
if (ref.kind === 'video') return 'referenceVideo';
|
|
3268
|
+
if (ref.flag === '--ref-end') return 'referenceImageEnd';
|
|
3269
|
+
if (ref.flag === '-c/--context') return `contextImage${Math.min(index + 1, 16)}`;
|
|
3270
|
+
return 'referenceImage';
|
|
3271
|
+
}
|
|
3272
|
+
|
|
3273
|
+
function apiMediaReferenceEndpoint(ref, action) {
|
|
3274
|
+
return ref.kind === 'image'
|
|
3275
|
+
? `/v1/image/${action}Url`
|
|
3276
|
+
: `/v1/media/${action}Url`;
|
|
3277
|
+
}
|
|
3278
|
+
|
|
3279
|
+
function apiMediaReferenceUrlPath(ref, file, index, action, jobId) {
|
|
3280
|
+
const params = new URLSearchParams();
|
|
3281
|
+
params.set('type', apiMediaReferenceUploadType(ref, index));
|
|
3282
|
+
params.set('jobId', jobId);
|
|
3283
|
+
params.set('contentType', file.mimeType);
|
|
3284
|
+
if (ref.kind === 'image') {
|
|
3285
|
+
params.set('imageId', `media_ref_${index + 1}`);
|
|
3286
|
+
} else {
|
|
3287
|
+
params.set('id', `media_ref_${index + 1}`);
|
|
3288
|
+
}
|
|
3289
|
+
return `${apiMediaReferenceEndpoint(ref, action)}?${params.toString()}`;
|
|
3290
|
+
}
|
|
3291
|
+
|
|
3292
|
+
function apiStoredMediaUrl(payload, key) {
|
|
3293
|
+
const data = extractApiEnvelopeData(payload);
|
|
3294
|
+
const value = data?.[key] || payload?.[key];
|
|
3295
|
+
if (typeof value === 'string' && value) return value;
|
|
3296
|
+
const err = new Error(`Sogni API did not return ${key} for media reference upload.`);
|
|
3297
|
+
err.code = 'MEDIA_UPLOAD_FAILED';
|
|
3298
|
+
err.details = { payload };
|
|
3299
|
+
throw err;
|
|
3300
|
+
}
|
|
3301
|
+
|
|
3302
|
+
async function putApiMediaUpload(uploadUrl, file) {
|
|
3303
|
+
const response = await fetch(uploadUrl, {
|
|
3304
|
+
method: 'PUT',
|
|
3305
|
+
headers: { 'Content-Type': file.mimeType },
|
|
3306
|
+
body: readFileSync(file.filePath),
|
|
3307
|
+
});
|
|
3308
|
+
if (!response.ok) {
|
|
3309
|
+
const err = new Error(`Failed to upload ${file.filename} (${response.status} ${response.statusText}).`);
|
|
3310
|
+
err.code = 'MEDIA_UPLOAD_FAILED';
|
|
3311
|
+
err.details = { uploadUrl, status: response.status, statusText: response.statusText };
|
|
3312
|
+
throw err;
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
|
|
3316
|
+
async function uploadLocalApiMediaReference(ref, index, apiKey) {
|
|
3317
|
+
if (!apiKey) {
|
|
3318
|
+
const err = new Error(`${ref.flag} local media references require SOGNI_API_KEY so the CLI can upload them before hosted execution.`);
|
|
3319
|
+
err.code = 'MISSING_API_KEY';
|
|
3320
|
+
throw err;
|
|
3321
|
+
}
|
|
3322
|
+
const file = localApiMediaReferenceFile(ref);
|
|
3323
|
+
const jobId = `sogni-agent-${Date.now()}-${index + 1}-${randomBytes(4).toString('hex')}`;
|
|
3324
|
+
const uploadPayload = await fetchApiJson(apiMediaReferenceUrlPath(ref, file, index, 'upload', jobId), { apiKey });
|
|
3325
|
+
const uploadUrl = apiStoredMediaUrl(uploadPayload, 'uploadUrl');
|
|
3326
|
+
await putApiMediaUpload(uploadUrl, file);
|
|
3327
|
+
const downloadPayload = await fetchApiJson(apiMediaReferenceUrlPath(ref, file, index, 'download', jobId), { apiKey });
|
|
3328
|
+
const url = apiStoredMediaUrl(downloadPayload, 'downloadUrl');
|
|
3329
|
+
return {
|
|
3330
|
+
url,
|
|
3331
|
+
filename: file.filename,
|
|
3332
|
+
byte_length: file.byteLength,
|
|
3333
|
+
mime_type: file.mimeType,
|
|
3334
|
+
prompt_label: file.filename,
|
|
3335
|
+
storage: {
|
|
3336
|
+
jobId,
|
|
3337
|
+
type: apiMediaReferenceUploadType(ref, index),
|
|
3338
|
+
},
|
|
3339
|
+
};
|
|
3340
|
+
}
|
|
3341
|
+
|
|
3342
|
+
async function buildApiMediaReferencePayloadItem(ref, index, apiKey) {
|
|
3343
|
+
const mimeType = mimeTypeForMediaReference(ref);
|
|
3344
|
+
const base = {
|
|
3345
|
+
id: `media_ref_${index + 1}`,
|
|
3346
|
+
source: 'cli',
|
|
3347
|
+
flag: ref.flag,
|
|
3348
|
+
kind: ref.kind,
|
|
3349
|
+
mime_type: mimeType,
|
|
3350
|
+
};
|
|
3351
|
+
if (isInlineApiMediaReference(ref.value)) {
|
|
3352
|
+
return {
|
|
3353
|
+
...base,
|
|
3354
|
+
dataUri: ref.value,
|
|
3355
|
+
filename: `inline-${base.id}`,
|
|
3356
|
+
prompt_label: `inline-${base.id}`,
|
|
3357
|
+
};
|
|
3358
|
+
}
|
|
3359
|
+
if (isRemoteApiMediaReference(ref.value)) {
|
|
3360
|
+
return {
|
|
3361
|
+
...base,
|
|
3362
|
+
url: ref.value,
|
|
3363
|
+
prompt_label: ref.value,
|
|
3364
|
+
};
|
|
3365
|
+
}
|
|
3366
|
+
const local = await uploadLocalApiMediaReference(ref, index, apiKey);
|
|
3367
|
+
return {
|
|
3368
|
+
...base,
|
|
3369
|
+
...local,
|
|
3370
|
+
filename: local.filename,
|
|
3371
|
+
mime_type: local.mime_type,
|
|
3372
|
+
};
|
|
3373
|
+
}
|
|
3374
|
+
|
|
3375
|
+
async function buildApiMediaReferencesPayload(refs = getApiModeMediaReferences(), { apiKey } = {}) {
|
|
3376
|
+
return Promise.all(refs.map((ref, index) => buildApiMediaReferencePayloadItem(ref, index, apiKey)));
|
|
3377
|
+
}
|
|
3378
|
+
|
|
3379
|
+
function formatApiMediaReferencesForPrompt(mediaReferences) {
|
|
3380
|
+
if (!mediaReferences.length) return '';
|
|
3381
|
+
const lines = mediaReferences.map(ref => {
|
|
3382
|
+
const label = ref.prompt_label || ref.url || ref.filename || ref.id;
|
|
3383
|
+
return `- ${ref.id} ${ref.kind} (${ref.flag}): ${label}`;
|
|
3384
|
+
});
|
|
3385
|
+
return `API media references:\n${lines.join('\n')}`;
|
|
3386
|
+
}
|
|
3387
|
+
|
|
3086
3388
|
function extractApiEnvelopeData(payload) {
|
|
3087
3389
|
return payload?.data && typeof payload.data === 'object' ? payload.data : payload;
|
|
3088
3390
|
}
|
|
@@ -3123,51 +3425,92 @@ async function imageDataUriFromPathOrUrl(pathOrUrl) {
|
|
|
3123
3425
|
return `data:${mimeType};base64,${buffer.toString('base64')}`;
|
|
3124
3426
|
}
|
|
3125
3427
|
|
|
3126
|
-
|
|
3428
|
+
function publicSkillApiSessionState(apiMediaReferences) {
|
|
3429
|
+
return {
|
|
3430
|
+
hasUploadedImage: apiMediaReferences.some(ref => ref.kind === 'image'),
|
|
3431
|
+
hasUploadedVideo: apiMediaReferences.some(ref => ref.kind === 'video'),
|
|
3432
|
+
hasUploadedAudio: apiMediaReferences.some(ref => ref.kind === 'audio'),
|
|
3433
|
+
};
|
|
3434
|
+
}
|
|
3435
|
+
|
|
3436
|
+
function publicSkillContractRuntimePayload(apiMediaReferences) {
|
|
3437
|
+
const runtime = createPublicSkillDefaultContractRuntime();
|
|
3438
|
+
const compiled = compilePublicSkillToolSurface({
|
|
3439
|
+
runtime,
|
|
3440
|
+
tools: PUBLIC_SKILL_DEFAULT_TOOL_DEFINITIONS,
|
|
3441
|
+
sessionState: publicSkillApiSessionState(apiMediaReferences),
|
|
3442
|
+
});
|
|
3443
|
+
return {
|
|
3444
|
+
version: 'default',
|
|
3445
|
+
turn_policy: {
|
|
3446
|
+
visible_tools: compiled.turnPolicy.visibleTools,
|
|
3447
|
+
forbidden_tools: compiled.turnPolicy.forbiddenTools,
|
|
3448
|
+
required_tools: compiled.turnPolicy.requiredTools,
|
|
3449
|
+
applied_policies: compiled.turnPolicy.appliedPolicies,
|
|
3450
|
+
rationale: compiled.turnPolicy.rationale,
|
|
3451
|
+
},
|
|
3452
|
+
contract_counts: {
|
|
3453
|
+
policies: runtime.policies.length,
|
|
3454
|
+
prompt_contracts: runtime.promptContracts.length,
|
|
3455
|
+
repair_recipes: runtime.repairRecipes.length,
|
|
3456
|
+
},
|
|
3457
|
+
};
|
|
3458
|
+
}
|
|
3459
|
+
|
|
3460
|
+
async function buildApiChatMessages(apiMediaRefs, apiMediaReferences) {
|
|
3127
3461
|
const system = options.apiSystemPrompt ||
|
|
3128
3462
|
'You are a concise creative production assistant. Use Sogni creative tools when they help produce concrete media.';
|
|
3129
|
-
const imageRefs =
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
options.
|
|
3133
|
-
|
|
3463
|
+
const imageRefs = apiMediaRefs.filter(ref => ref.kind === 'image');
|
|
3464
|
+
const nonImageRefs = apiMediaReferences.filter(ref => ref.kind !== 'image');
|
|
3465
|
+
const promptText = [
|
|
3466
|
+
options.prompt || 'Describe the attached media.',
|
|
3467
|
+
formatApiMediaReferencesForPrompt(nonImageRefs)
|
|
3468
|
+
].filter(Boolean).join('\n\n');
|
|
3134
3469
|
|
|
3135
3470
|
const messages = [{ role: 'system', content: system }];
|
|
3136
3471
|
if (imageRefs.length === 0) {
|
|
3137
|
-
messages.push({ role: 'user', content:
|
|
3472
|
+
messages.push({ role: 'user', content: promptText });
|
|
3138
3473
|
return messages;
|
|
3139
3474
|
}
|
|
3140
3475
|
|
|
3141
|
-
|
|
3142
|
-
const err = new Error(
|
|
3143
|
-
'--api-chat with server-side tool execution does not currently support image references. ' +
|
|
3144
|
-
'Use the direct CLI path for uploaded-media workflows, or pass --no-api-tool-execution for vision-only chat/planning.'
|
|
3145
|
-
);
|
|
3146
|
-
err.code = 'UNSUPPORTED_API_UPLOAD_EXECUTION';
|
|
3147
|
-
throw err;
|
|
3148
|
-
}
|
|
3149
|
-
|
|
3150
|
-
const content = [{ type: 'text', text: options.prompt || 'Describe the attached media.' }];
|
|
3476
|
+
const content = [{ type: 'text', text: promptText }];
|
|
3151
3477
|
for (const ref of imageRefs) {
|
|
3152
|
-
content.push({ type: 'image_url', image_url: { url: await imageDataUriFromPathOrUrl(ref) } });
|
|
3478
|
+
content.push({ type: 'image_url', image_url: { url: await imageDataUriFromPathOrUrl(ref.value) } });
|
|
3153
3479
|
}
|
|
3154
3480
|
messages.push({ role: 'user', content });
|
|
3155
3481
|
return messages;
|
|
3156
3482
|
}
|
|
3157
3483
|
|
|
3484
|
+
function apiChatTemplateKwargs() {
|
|
3485
|
+
if (typeof options.apiThinking !== 'boolean') return null;
|
|
3486
|
+
return { enable_thinking: options.apiThinking };
|
|
3487
|
+
}
|
|
3488
|
+
|
|
3158
3489
|
async function runApiChat(log) {
|
|
3159
3490
|
const creds = loadCredentials();
|
|
3160
3491
|
const apiKey = requireApiKeyCredentials(creds, '--api-chat');
|
|
3492
|
+
const apiMediaRefs = getApiModeMediaReferences();
|
|
3493
|
+
const apiMediaReferences = await buildApiMediaReferencesPayload(apiMediaRefs, { apiKey });
|
|
3494
|
+
const messages = sanitizeMessagesForLlm(await buildApiChatMessages(apiMediaRefs, apiMediaReferences));
|
|
3495
|
+
const chatTemplateKwargs = apiChatTemplateKwargs();
|
|
3496
|
+
const publicSkillRuntime = publicSkillContractRuntimePayload(apiMediaReferences);
|
|
3161
3497
|
const body = {
|
|
3162
3498
|
model: options.llmModel || DEFAULT_LLM_MODEL,
|
|
3163
|
-
messages
|
|
3499
|
+
messages,
|
|
3164
3500
|
temperature: 0.4,
|
|
3165
|
-
max_tokens: 1600,
|
|
3501
|
+
max_tokens: options.apiMaxTokens || 1600,
|
|
3166
3502
|
token_type: options.tokenType || 'spark',
|
|
3167
3503
|
app_source: SOGNI_APP_SOURCE,
|
|
3168
3504
|
appSource: SOGNI_APP_SOURCE,
|
|
3169
3505
|
sogni_tools: options.apiTools,
|
|
3170
|
-
sogni_tool_execution: options.apiToolExecution
|
|
3506
|
+
sogni_tool_execution: options.apiToolExecution,
|
|
3507
|
+
public_skill_contract_runtime: publicSkillRuntime,
|
|
3508
|
+
...(options.apiTaskProfile ? { task_profile: options.apiTaskProfile } : {}),
|
|
3509
|
+
...(chatTemplateKwargs ? { chat_template_kwargs: chatTemplateKwargs } : {}),
|
|
3510
|
+
...(apiMediaReferences.length > 0 ? {
|
|
3511
|
+
api_media_references: apiMediaReferences,
|
|
3512
|
+
media_references: apiMediaReferences,
|
|
3513
|
+
} : {})
|
|
3171
3514
|
};
|
|
3172
3515
|
const payload = await fetchApiJson('/v1/chat/completions', {
|
|
3173
3516
|
apiKey,
|
|
@@ -3208,22 +3551,36 @@ async function runApiChat(log) {
|
|
|
3208
3551
|
}
|
|
3209
3552
|
}
|
|
3210
3553
|
|
|
3211
|
-
function
|
|
3554
|
+
function parseJsonArgument(raw, label, code = 'INVALID_JSON_INPUT') {
|
|
3212
3555
|
if (!raw) return null;
|
|
3213
3556
|
const sourcePath = raw.startsWith('@') ? raw.slice(1) : raw;
|
|
3214
3557
|
const expanded = expandHomePath(sourcePath);
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3558
|
+
let text;
|
|
3559
|
+
if (raw.startsWith('@') || existsSync(expanded)) {
|
|
3560
|
+
try {
|
|
3561
|
+
text = readFileSync(expanded, 'utf8');
|
|
3562
|
+
} catch (error) {
|
|
3563
|
+
const err = new Error(`Unable to read ${label} file: ${error?.message || String(error)}`);
|
|
3564
|
+
err.code = code;
|
|
3565
|
+
err.details = { path: expanded };
|
|
3566
|
+
throw err;
|
|
3567
|
+
}
|
|
3568
|
+
} else {
|
|
3569
|
+
text = raw;
|
|
3570
|
+
}
|
|
3218
3571
|
try {
|
|
3219
3572
|
return JSON.parse(text);
|
|
3220
3573
|
} catch (error) {
|
|
3221
|
-
const err = new Error(`Invalid
|
|
3222
|
-
err.code =
|
|
3574
|
+
const err = new Error(`Invalid ${label} JSON: ${error?.message || String(error)}`);
|
|
3575
|
+
err.code = code;
|
|
3223
3576
|
throw err;
|
|
3224
3577
|
}
|
|
3225
3578
|
}
|
|
3226
3579
|
|
|
3580
|
+
function parseWorkflowInput(raw) {
|
|
3581
|
+
return parseJsonArgument(raw, '--workflow-input', 'INVALID_WORKFLOW_INPUT');
|
|
3582
|
+
}
|
|
3583
|
+
|
|
3227
3584
|
function buildImageToVideoWorkflowInput() {
|
|
3228
3585
|
const parsed = parseWorkflowInput(options.apiWorkflowInput);
|
|
3229
3586
|
if (parsed) return parsed;
|
|
@@ -3257,6 +3614,14 @@ function buildHostedToolSequenceWorkflowInput() {
|
|
|
3257
3614
|
return parsed;
|
|
3258
3615
|
}
|
|
3259
3616
|
|
|
3617
|
+
function buildCreativePlanWorkflowInput() {
|
|
3618
|
+
const parsed = parseWorkflowInput(options.apiWorkflowInput);
|
|
3619
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed) && options.apiWorkflowTitle && !parsed.title) {
|
|
3620
|
+
parsed.title = options.apiWorkflowTitle;
|
|
3621
|
+
}
|
|
3622
|
+
return parsed;
|
|
3623
|
+
}
|
|
3624
|
+
|
|
3260
3625
|
function storyboardWorkflowImageQualityFromCli() {
|
|
3261
3626
|
if (!cliSet.quality || !options.quality) return undefined;
|
|
3262
3627
|
if (options.quality === 'pro') return 'high';
|
|
@@ -3355,17 +3720,21 @@ function buildStoryboardStorylineMessages() {
|
|
|
3355
3720
|
}
|
|
3356
3721
|
|
|
3357
3722
|
async function generateStoryboardWorkflowStoryline(apiKey) {
|
|
3723
|
+
const messages = sanitizeMessagesForLlm(buildStoryboardStorylineMessages());
|
|
3724
|
+
const chatTemplateKwargs = apiChatTemplateKwargs();
|
|
3358
3725
|
const payload = await fetchApiJson('/v1/chat/completions', {
|
|
3359
3726
|
apiKey,
|
|
3360
3727
|
method: 'POST',
|
|
3361
3728
|
body: {
|
|
3362
3729
|
model: options.llmModel || DEFAULT_LLM_MODEL,
|
|
3363
|
-
messages
|
|
3730
|
+
messages,
|
|
3364
3731
|
temperature: 0.45,
|
|
3365
|
-
max_tokens: 1800,
|
|
3732
|
+
max_tokens: options.apiMaxTokens || 1800,
|
|
3366
3733
|
token_type: options.tokenType || 'spark',
|
|
3367
3734
|
app_source: SOGNI_APP_SOURCE,
|
|
3368
3735
|
appSource: SOGNI_APP_SOURCE,
|
|
3736
|
+
...(options.apiTaskProfile ? { task_profile: options.apiTaskProfile } : {}),
|
|
3737
|
+
...(chatTemplateKwargs ? { chat_template_kwargs: chatTemplateKwargs } : {}),
|
|
3369
3738
|
sogni_tools: false,
|
|
3370
3739
|
sogni_tool_execution: false
|
|
3371
3740
|
}
|
|
@@ -3423,6 +3792,104 @@ function eventsFromPayload(payload) {
|
|
|
3423
3792
|
return data?.events || payload?.events || [];
|
|
3424
3793
|
}
|
|
3425
3794
|
|
|
3795
|
+
function modelsFromPayload(payload) {
|
|
3796
|
+
if (Array.isArray(payload?.data)) return payload.data;
|
|
3797
|
+
const data = extractApiEnvelopeData(payload);
|
|
3798
|
+
const models = Array.isArray(data) ? data : data?.models;
|
|
3799
|
+
return Array.isArray(models) ? models : [];
|
|
3800
|
+
}
|
|
3801
|
+
|
|
3802
|
+
async function runApiModels() {
|
|
3803
|
+
const creds = loadCredentials();
|
|
3804
|
+
const type = 'api-models';
|
|
3805
|
+
const action = options.apiModelAction || 'list';
|
|
3806
|
+
const apiKey = requireApiKeyCredentials(creds, action === 'get' ? '--get-api-model' : '--list-api-models');
|
|
3807
|
+
const payload = action === 'get'
|
|
3808
|
+
? await fetchApiJson(`/v1/models/${encodeURIComponent(options.apiModelId)}`, { apiKey })
|
|
3809
|
+
: await fetchApiJson('/v1/models', { apiKey });
|
|
3810
|
+
|
|
3811
|
+
if (options.json) {
|
|
3812
|
+
console.log(JSON.stringify({
|
|
3813
|
+
success: true,
|
|
3814
|
+
type,
|
|
3815
|
+
action,
|
|
3816
|
+
...(action === 'get' ? { model: payload } : { models: modelsFromPayload(payload) }),
|
|
3817
|
+
raw: payload
|
|
3818
|
+
}));
|
|
3819
|
+
return;
|
|
3820
|
+
}
|
|
3821
|
+
|
|
3822
|
+
if (action === 'get') {
|
|
3823
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
3824
|
+
return;
|
|
3825
|
+
}
|
|
3826
|
+
|
|
3827
|
+
const models = modelsFromPayload(payload);
|
|
3828
|
+
for (const model of models) {
|
|
3829
|
+
console.log(`${model.id || model.modelId || model.name || '(unknown)'}\t${model.owned_by || model.displayName || ''}`);
|
|
3830
|
+
}
|
|
3831
|
+
}
|
|
3832
|
+
|
|
3833
|
+
function recordsFromReplayPayload(payload) {
|
|
3834
|
+
const data = extractApiEnvelopeData(payload);
|
|
3835
|
+
return Array.isArray(data?.records) ? data.records : Array.isArray(payload?.records) ? payload.records : [];
|
|
3836
|
+
}
|
|
3837
|
+
|
|
3838
|
+
function replayRecordFromPayload(payload) {
|
|
3839
|
+
const data = extractApiEnvelopeData(payload);
|
|
3840
|
+
return data?.record || payload?.record || payload;
|
|
3841
|
+
}
|
|
3842
|
+
|
|
3843
|
+
async function runApiReplay() {
|
|
3844
|
+
const creds = loadCredentials();
|
|
3845
|
+
const type = 'api-replay';
|
|
3846
|
+
const action = options.apiReplayAction || 'list';
|
|
3847
|
+
const replayModeLabel = action === 'get'
|
|
3848
|
+
? '--get-replay'
|
|
3849
|
+
: action === 'ingest'
|
|
3850
|
+
? '--ingest-replay'
|
|
3851
|
+
: '--list-replays';
|
|
3852
|
+
const apiKey = requireApiKeyCredentials(creds, replayModeLabel);
|
|
3853
|
+
let payload;
|
|
3854
|
+
|
|
3855
|
+
if (action === 'list') {
|
|
3856
|
+
payload = await fetchApiJson(`/v1/replay/records?limit=${encodeURIComponent(options.apiReplayLimit || 50)}`, { apiKey });
|
|
3857
|
+
const records = recordsFromReplayPayload(payload);
|
|
3858
|
+
if (options.json) {
|
|
3859
|
+
console.log(JSON.stringify({ success: true, type, action, records, raw: payload }));
|
|
3860
|
+
} else {
|
|
3861
|
+
for (const record of records) {
|
|
3862
|
+
console.log(`${record.runId || record.run_id || '(unknown)'}\t${record.modelId || record.model_id || '-'}\t${record.rounds ?? '-'}\t${record.userRequest || record.user_request || ''}`);
|
|
3863
|
+
}
|
|
3864
|
+
}
|
|
3865
|
+
return;
|
|
3866
|
+
}
|
|
3867
|
+
|
|
3868
|
+
if (action === 'get') {
|
|
3869
|
+
payload = await fetchApiJson(`/v1/replay/records/${encodeURIComponent(options.apiReplayId)}`, { apiKey });
|
|
3870
|
+
const record = replayRecordFromPayload(payload);
|
|
3871
|
+
if (options.json) {
|
|
3872
|
+
console.log(JSON.stringify({ success: true, type, action, runId: options.apiReplayId, record, raw: payload }));
|
|
3873
|
+
} else {
|
|
3874
|
+
console.log(JSON.stringify(record, null, 2));
|
|
3875
|
+
}
|
|
3876
|
+
return;
|
|
3877
|
+
}
|
|
3878
|
+
|
|
3879
|
+
const recordInput = parseJsonArgument(options.apiReplayInput, '--ingest-replay', 'INVALID_REPLAY_INPUT');
|
|
3880
|
+
payload = await fetchApiJson('/v1/replay/records', {
|
|
3881
|
+
apiKey,
|
|
3882
|
+
method: 'POST',
|
|
3883
|
+
body: recordInput
|
|
3884
|
+
});
|
|
3885
|
+
const result = extractApiEnvelopeData(payload);
|
|
3886
|
+
if (options.json) {
|
|
3887
|
+
console.log(JSON.stringify({ success: true, type, action, result, raw: payload }));
|
|
3888
|
+
} else {
|
|
3889
|
+
console.log(`Replay record ingested: ${result.runId || result.run_id || recordInput?.run_id || '(unknown)'}`);
|
|
3890
|
+
}
|
|
3891
|
+
}
|
|
3892
|
+
|
|
3426
3893
|
function printWorkflowSummary(workflow) {
|
|
3427
3894
|
console.log(`Workflow: ${workflow.workflowId || workflow.id || '(unknown)'}`);
|
|
3428
3895
|
if (workflow.kind) console.log(`Kind: ${workflow.kind}`);
|
|
@@ -3536,7 +4003,13 @@ async function runApiWorkflow() {
|
|
|
3536
4003
|
return;
|
|
3537
4004
|
}
|
|
3538
4005
|
|
|
3539
|
-
if (
|
|
4006
|
+
if (
|
|
4007
|
+
options.apiWorkflowAction === 'get'
|
|
4008
|
+
|| options.apiWorkflowAction === 'events'
|
|
4009
|
+
|| options.apiWorkflowAction === 'stream'
|
|
4010
|
+
|| options.apiWorkflowAction === 'cancel'
|
|
4011
|
+
|| options.apiWorkflowAction === 'resume'
|
|
4012
|
+
) {
|
|
3540
4013
|
const id = options.apiWorkflowId;
|
|
3541
4014
|
if (!id) {
|
|
3542
4015
|
const err = new Error('Workflow id is required.');
|
|
@@ -3554,10 +4027,12 @@ async function runApiWorkflow() {
|
|
|
3554
4027
|
? `/v1/creative-agent/workflows/${encodeURIComponent(id)}/events`
|
|
3555
4028
|
: options.apiWorkflowAction === 'cancel'
|
|
3556
4029
|
? `/v1/creative-agent/workflows/${encodeURIComponent(id)}/cancel`
|
|
3557
|
-
:
|
|
4030
|
+
: options.apiWorkflowAction === 'resume'
|
|
4031
|
+
? `/v1/creative-agent/workflows/${encodeURIComponent(id)}/resume`
|
|
4032
|
+
: `/v1/creative-agent/workflows/${encodeURIComponent(id)}`;
|
|
3558
4033
|
payload = await fetchApiJson(path, {
|
|
3559
4034
|
apiKey,
|
|
3560
|
-
method: options.apiWorkflowAction === 'cancel' ? 'POST' : 'GET'
|
|
4035
|
+
method: options.apiWorkflowAction === 'cancel' || options.apiWorkflowAction === 'resume' ? 'POST' : 'GET'
|
|
3561
4036
|
});
|
|
3562
4037
|
if (options.apiWorkflowAction === 'events') {
|
|
3563
4038
|
const events = eventsFromPayload(payload);
|
|
@@ -3571,6 +4046,8 @@ async function runApiWorkflow() {
|
|
|
3571
4046
|
return;
|
|
3572
4047
|
}
|
|
3573
4048
|
|
|
4049
|
+
const apiMediaReferences = await buildApiMediaReferencesPayload(undefined, { apiKey });
|
|
4050
|
+
const publicSkillRuntime = publicSkillContractRuntimePayload(apiMediaReferences);
|
|
3574
4051
|
const requestedKind = options.apiWorkflowKind || 'image_to_video';
|
|
3575
4052
|
let kind = requestedKind;
|
|
3576
4053
|
let input;
|
|
@@ -3586,7 +4063,9 @@ async function runApiWorkflow() {
|
|
|
3586
4063
|
} else {
|
|
3587
4064
|
input = requestedKind === 'hosted_tool_sequence'
|
|
3588
4065
|
? buildHostedToolSequenceWorkflowInput()
|
|
3589
|
-
:
|
|
4066
|
+
: requestedKind === 'creative_plan'
|
|
4067
|
+
? buildCreativePlanWorkflowInput()
|
|
4068
|
+
: buildImageToVideoWorkflowInput();
|
|
3590
4069
|
}
|
|
3591
4070
|
|
|
3592
4071
|
payload = await fetchApiJson('/v1/creative-agent/workflows', {
|
|
@@ -3599,9 +4078,19 @@ async function runApiWorkflow() {
|
|
|
3599
4078
|
kind,
|
|
3600
4079
|
input,
|
|
3601
4080
|
...(options.apiWorkflowIdempotencyKey ? { idempotency_key: options.apiWorkflowIdempotencyKey } : {}),
|
|
4081
|
+
...(apiMediaReferences.length > 0 ? {
|
|
4082
|
+
api_media_references: apiMediaReferences,
|
|
4083
|
+
media_references: apiMediaReferences,
|
|
4084
|
+
} : {}),
|
|
4085
|
+
...(options.apiWorkflowMaxCost !== null ? {
|
|
4086
|
+
max_estimated_capacity_units: options.apiWorkflowMaxCost,
|
|
4087
|
+
cost_ceiling: options.apiWorkflowMaxCost,
|
|
4088
|
+
} : {}),
|
|
4089
|
+
...(options.apiWorkflowConfirmCost !== null ? { confirm_cost: options.apiWorkflowConfirmCost } : {}),
|
|
3602
4090
|
token_type: tokenType,
|
|
3603
4091
|
app_source: SOGNI_APP_SOURCE,
|
|
3604
|
-
appSource: SOGNI_APP_SOURCE
|
|
4092
|
+
appSource: SOGNI_APP_SOURCE,
|
|
4093
|
+
public_skill_contract_runtime: publicSkillRuntime
|
|
3605
4094
|
}
|
|
3606
4095
|
});
|
|
3607
4096
|
const workflow = workflowFromPayload(payload);
|
|
@@ -4940,6 +5429,16 @@ async function main() {
|
|
|
4940
5429
|
}
|
|
4941
5430
|
}
|
|
4942
5431
|
|
|
5432
|
+
if (options.apiModelAction) {
|
|
5433
|
+
await runApiModels();
|
|
5434
|
+
return;
|
|
5435
|
+
}
|
|
5436
|
+
|
|
5437
|
+
if (options.apiReplayAction) {
|
|
5438
|
+
await runApiReplay();
|
|
5439
|
+
return;
|
|
5440
|
+
}
|
|
5441
|
+
|
|
4943
5442
|
if (options.apiChat) {
|
|
4944
5443
|
await runApiChat(log);
|
|
4945
5444
|
return;
|
|
@@ -6019,11 +6518,11 @@ async function main() {
|
|
|
6019
6518
|
exitCode = 1;
|
|
6020
6519
|
const shouldJson = options.json || IS_OPENCLAW_INVOCATION;
|
|
6021
6520
|
if (shouldJson) {
|
|
6022
|
-
const payload = {
|
|
6521
|
+
const payload = addCanonicalErrorFields({
|
|
6023
6522
|
success: false,
|
|
6024
6523
|
error: error.message,
|
|
6025
6524
|
prompt: options.prompt ?? null
|
|
6026
|
-
};
|
|
6525
|
+
}, error);
|
|
6027
6526
|
if (error.code) payload.errorCode = error.code;
|
|
6028
6527
|
if (error.details) payload.errorDetails = error.details;
|
|
6029
6528
|
if (error.hint) payload.hint = error.hint;
|