@302ai/media-studio-core 0.1.0-beta.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.
@@ -0,0 +1,395 @@
1
+ import { isObject } from "es-toolkit/compat";
2
+ import { isString } from "es-toolkit/predicate";
3
+ const ASPECT_RATIO_PATTERN = /aspect_ratio:\s*([^\s,;]+)/i;
4
+ const MODEL_PATTERN = /(?:^|\n)\s*-\s*model:\s*([^\s,;]+)/i;
5
+ const DURATION_PATTERN = /(?:^|\n)\s*-\s*duration:\s*([^\s,;]+)/i;
6
+ const OPERATION_PATTERN = /(?:^|\n)\s*-\s*operation:\s*([^\s,;]+)/i;
7
+ const CREATE_TOOL_META = {
8
+ image_create: { category: "image", defaultLabel: "Generating image" },
9
+ video_create: { category: "video", defaultLabel: "Generating video" },
10
+ tts_create: { category: "speech", defaultLabel: "Synthesizing speech" },
11
+ sfx_create: { category: "sfx", defaultLabel: "Generating sound effects" },
12
+ stt_transcribe: { category: "stt", defaultLabel: "Transcribing audio" },
13
+ three_d_create: { category: "3d", defaultLabel: "Generating 3D model" },
14
+ song_create: { category: "song", defaultLabel: "Generating song" },
15
+ };
16
+ const MODEL_TOOL_NAMES = new Set([
17
+ "model_list",
18
+ "model_params",
19
+ "models_3d",
20
+ "stt_models",
21
+ "tts_providers",
22
+ "tts_voices",
23
+ "tts_refresh",
24
+ ]);
25
+ function isRecord(value) {
26
+ return isObject(value);
27
+ }
28
+ function truncate(str, maxLen) {
29
+ if (str.length <= maxLen)
30
+ return str;
31
+ return `${str.slice(0, maxLen)}...`;
32
+ }
33
+ function getDisplayFileName(filePath) {
34
+ if (!filePath)
35
+ return "";
36
+ return filePath.split(/[/\\]/).pop() || filePath;
37
+ }
38
+ function extractFromPrompt(prompt, pattern) {
39
+ if (!isString(prompt))
40
+ return undefined;
41
+ const match = pattern.exec(prompt);
42
+ return match?.[1];
43
+ }
44
+ /**
45
+ * 1:1 normalization parity with main project's `normalizeMcpToolName`.
46
+ * Strips `tool-` prefix, lowercases, resolves MCP namespaces (`mcp__server__name`),
47
+ * and trims trailing `output` suffix.
48
+ */
49
+ export function normalizeMcpToolName(toolName) {
50
+ const raw = toolName.startsWith("tool-") ? toolName.slice(5) : toolName;
51
+ const lower = raw.toLowerCase();
52
+ const separatorIndex = lower.lastIndexOf("__");
53
+ let normalized = separatorIndex >= 0 ? lower.slice(separatorIndex + 2) : lower;
54
+ if (normalized.endsWith("output") && normalized !== "output") {
55
+ normalized = normalized.slice(0, -6);
56
+ }
57
+ return normalized;
58
+ }
59
+ /**
60
+ * Parses and formats a tool call into a structured milestone and dynamic action label.
61
+ * Fully aligned 1:1 with the main project's tool renderer hierarchy.
62
+ */
63
+ export function formatToolMilestone(toolName, args) {
64
+ const normalized = normalizeMcpToolName(toolName);
65
+ const input = isRecord(args) ? args : {};
66
+ // 1. Agent dispatch (Agent / task)
67
+ if (normalized === "agent" || normalized === "task") {
68
+ const subagentType = isString(input.subagent_type)
69
+ ? input.subagent_type
70
+ : "subagent";
71
+ const description = isString(input.description) ? input.description : "";
72
+ const model = isString(input.model)
73
+ ? input.model
74
+ : extractFromPrompt(input.prompt, MODEL_PATTERN);
75
+ const aspectRatio = isString(input.aspect_ratio)
76
+ ? input.aspect_ratio
77
+ : extractFromPrompt(input.prompt, ASPECT_RATIO_PATTERN);
78
+ const duration = isString(input.duration)
79
+ ? input.duration
80
+ : extractFromPrompt(input.prompt, DURATION_PATTERN);
81
+ const operation = isString(input.operation)
82
+ ? input.operation
83
+ : extractFromPrompt(input.prompt, OPERATION_PATTERN);
84
+ const descPart = description ? `: ${description}` : "";
85
+ const tags = [];
86
+ if (model)
87
+ tags.push(`model: ${model}`);
88
+ if (aspectRatio)
89
+ tags.push(aspectRatio);
90
+ if (duration)
91
+ tags.push(duration);
92
+ const tagsPart = tags.length > 0 ? ` (${tags.join(", ")})` : "";
93
+ const opPart = operation ? ` <${operation.toUpperCase()}>` : "";
94
+ const milestoneLine = `[step] Dispatched ${subagentType}${descPart}${tagsPart}${opPart}`;
95
+ const actionLabel = model
96
+ ? `Executing ${subagentType} (${model})...`
97
+ : `Executing ${subagentType}...`;
98
+ return {
99
+ rawName: toolName,
100
+ normalizedName: normalized,
101
+ category: "agent",
102
+ milestoneLine,
103
+ actionLabel,
104
+ details: {
105
+ subagentType,
106
+ description: description || undefined,
107
+ model,
108
+ aspectRatio,
109
+ duration,
110
+ operation,
111
+ },
112
+ };
113
+ }
114
+ // 2. Multimodal Creation family (image_create, video_create, song_create, etc.)
115
+ if (normalized in CREATE_TOOL_META) {
116
+ const meta = CREATE_TOOL_META[normalized];
117
+ const model = isString(input.model)
118
+ ? input.model
119
+ : extractFromPrompt(input.prompt, MODEL_PATTERN);
120
+ const aspectRatio = isString(input.aspect_ratio)
121
+ ? input.aspect_ratio
122
+ : extractFromPrompt(input.prompt, ASPECT_RATIO_PATTERN);
123
+ const duration = isString(input.duration)
124
+ ? input.duration
125
+ : typeof input.duration === "number"
126
+ ? `${input.duration}s`
127
+ : isString(input.seconds)
128
+ ? `${input.seconds}s`
129
+ : extractFromPrompt(input.prompt, DURATION_PATTERN);
130
+ const resolution = isString(input.resolution)
131
+ ? input.resolution
132
+ : isString(input.size)
133
+ ? input.size
134
+ : undefined;
135
+ const voice = isString(input.voice)
136
+ ? input.voice
137
+ : isString(input.speaker)
138
+ ? input.speaker
139
+ : undefined;
140
+ let operation = isString(input.operation)
141
+ ? input.operation
142
+ : extractFromPrompt(input.prompt, OPERATION_PATTERN);
143
+ if (!operation) {
144
+ if (input.image)
145
+ operation = "i2i";
146
+ else if (input.video)
147
+ operation = "v2v";
148
+ }
149
+ const specs = [];
150
+ if (aspectRatio)
151
+ specs.push(aspectRatio);
152
+ if (duration)
153
+ specs.push(duration);
154
+ if (resolution)
155
+ specs.push(resolution);
156
+ if (voice)
157
+ specs.push(`voice: ${voice}`);
158
+ const modelPart = model ? ` (model: ${model})` : "";
159
+ const opPart = operation ? ` <${operation.toUpperCase()}>` : "";
160
+ const specsPart = specs.length > 0 ? ` [${specs.join(", ")}]` : "";
161
+ const milestoneLine = `[step] Executing ${normalized}${modelPart}${opPart}${specsPart}`;
162
+ const actionLabel = model
163
+ ? `${meta.defaultLabel} (${model})...`
164
+ : `${meta.defaultLabel}...`;
165
+ return {
166
+ rawName: toolName,
167
+ normalizedName: normalized,
168
+ category: "create",
169
+ milestoneLine,
170
+ actionLabel,
171
+ details: {
172
+ model,
173
+ aspectRatio,
174
+ duration,
175
+ resolution,
176
+ voice,
177
+ operation,
178
+ },
179
+ };
180
+ }
181
+ // 3. Model & Capability Discovery tools
182
+ if (MODEL_TOOL_NAMES.has(normalized)) {
183
+ const kind = isString(input.kind) ? input.kind : undefined;
184
+ const model = isString(input.model) ? input.model : undefined;
185
+ let milestoneLine = `[step] Querying models (${normalized})...`;
186
+ let actionLabel = "Querying models...";
187
+ if (normalized === "model_list") {
188
+ milestoneLine = kind
189
+ ? `[step] Querying available models (${kind})...`
190
+ : "[step] Querying available models...";
191
+ actionLabel = kind
192
+ ? `Querying available models (${kind})...`
193
+ : "Querying available models...";
194
+ }
195
+ else if (normalized === "model_params" && model) {
196
+ milestoneLine = `[step] Querying parameters for model: ${model}...`;
197
+ actionLabel = `Inspecting model ${model}...`;
198
+ }
199
+ return {
200
+ rawName: toolName,
201
+ normalizedName: normalized,
202
+ category: "model",
203
+ milestoneLine,
204
+ actionLabel,
205
+ details: {
206
+ model,
207
+ target: kind,
208
+ },
209
+ };
210
+ }
211
+ // 4. Search & Fetch tools
212
+ if (normalized === "web_search") {
213
+ const query = isString(input.query)
214
+ ? input.query
215
+ : isString(input.q)
216
+ ? input.q
217
+ : "";
218
+ const queryPart = query ? `: ${query}` : "";
219
+ return {
220
+ rawName: toolName,
221
+ normalizedName: normalized,
222
+ category: "search",
223
+ milestoneLine: `[step] Searching web${queryPart}`,
224
+ actionLabel: query
225
+ ? `Searching web: ${truncate(query, 25)}...`
226
+ : "Searching web...",
227
+ details: { query: query || undefined },
228
+ };
229
+ }
230
+ if (normalized === "web_fetch" || normalized === "download_file") {
231
+ const url = isString(input.url) ? input.url : "";
232
+ const target = url ? ` ${url}` : "";
233
+ return {
234
+ rawName: toolName,
235
+ normalizedName: normalized,
236
+ category: "search",
237
+ milestoneLine: `[step] ${normalized === "download_file" ? "Downloading file" : "Fetching URL"}${target}`,
238
+ actionLabel: normalized === "download_file"
239
+ ? "Downloading file..."
240
+ : "Fetching web content...",
241
+ details: { target: url || undefined },
242
+ };
243
+ }
244
+ if (normalized === "document_parse") {
245
+ const target = isString(input.file_path)
246
+ ? input.file_path
247
+ : isString(input.url)
248
+ ? input.url
249
+ : "";
250
+ const display = getDisplayFileName(target);
251
+ return {
252
+ rawName: toolName,
253
+ normalizedName: normalized,
254
+ category: "search",
255
+ milestoneLine: display
256
+ ? `[step] Parsing document: ${display}`
257
+ : "[step] Parsing document...",
258
+ actionLabel: display ? `Parsing ${display}...` : "Parsing document...",
259
+ details: { target: target || undefined },
260
+ };
261
+ }
262
+ if (normalized === "image_analyze" || normalized === "video_analyze") {
263
+ const mediaType = normalized === "image_analyze" ? "image" : "video";
264
+ return {
265
+ rawName: toolName,
266
+ normalizedName: normalized,
267
+ category: "search",
268
+ milestoneLine: `[step] Analyzing ${mediaType}...`,
269
+ actionLabel: `Analyzing ${mediaType}...`,
270
+ details: {},
271
+ };
272
+ }
273
+ if (normalized === "song_lyrics") {
274
+ const title = isString(input.title)
275
+ ? input.title
276
+ : isString(input.query)
277
+ ? input.query
278
+ : "";
279
+ return {
280
+ rawName: toolName,
281
+ normalizedName: normalized,
282
+ category: "search",
283
+ milestoneLine: title
284
+ ? `[step] Searching lyrics: ${title}`
285
+ : "[step] Searching song lyrics...",
286
+ actionLabel: "Searching song lyrics...",
287
+ details: { query: title || undefined },
288
+ };
289
+ }
290
+ // 5. File & Shell tools
291
+ if (normalized === "read" ||
292
+ normalized === "read_file" ||
293
+ normalized === "view_file") {
294
+ const path = isString(input.file_path)
295
+ ? input.file_path
296
+ : isString(input.path)
297
+ ? input.path
298
+ : "";
299
+ const display = getDisplayFileName(path);
300
+ return {
301
+ rawName: toolName,
302
+ normalizedName: normalized,
303
+ category: "file",
304
+ milestoneLine: display
305
+ ? `[step] Reading file: ${display}`
306
+ : "[step] Reading file...",
307
+ actionLabel: display ? `Reading ${display}...` : "Reading file...",
308
+ details: { target: path || undefined },
309
+ };
310
+ }
311
+ if (normalized === "write" ||
312
+ normalized === "write_file" ||
313
+ normalized === "write_to_file" ||
314
+ normalized === "create_file") {
315
+ const path = isString(input.file_path)
316
+ ? input.file_path
317
+ : isString(input.path)
318
+ ? input.path
319
+ : "";
320
+ const display = getDisplayFileName(path);
321
+ return {
322
+ rawName: toolName,
323
+ normalizedName: normalized,
324
+ category: "file",
325
+ milestoneLine: display
326
+ ? `[step] Writing file: ${display}`
327
+ : "[step] Writing file...",
328
+ actionLabel: display ? `Writing ${display}...` : "Writing file...",
329
+ details: { target: path || undefined },
330
+ };
331
+ }
332
+ if (normalized === "edit" ||
333
+ normalized === "edit_file" ||
334
+ normalized === "replace_file_content" ||
335
+ normalized === "multi_replace_file_content") {
336
+ const path = isString(input.file_path)
337
+ ? input.file_path
338
+ : isString(input.path)
339
+ ? input.path
340
+ : "";
341
+ const display = getDisplayFileName(path);
342
+ return {
343
+ rawName: toolName,
344
+ normalizedName: normalized,
345
+ category: "file",
346
+ milestoneLine: display
347
+ ? `[step] Editing file: ${display}`
348
+ : "[step] Editing file...",
349
+ actionLabel: display ? `Editing ${display}...` : "Editing file...",
350
+ details: { target: path || undefined },
351
+ };
352
+ }
353
+ if (normalized === "bash") {
354
+ const cmd = isString(input.command) ? input.command : "";
355
+ return {
356
+ rawName: toolName,
357
+ normalizedName: normalized,
358
+ category: "bash",
359
+ milestoneLine: cmd
360
+ ? `[step] Running bash: ${truncate(cmd, 40)}`
361
+ : "[step] Running bash command...",
362
+ actionLabel: "Running bash command...",
363
+ details: { target: cmd || undefined },
364
+ };
365
+ }
366
+ // 6. Skill tools
367
+ if (normalized === "skill" || normalized === "skill_created") {
368
+ const skillName = isString(input.skill_name)
369
+ ? input.skill_name
370
+ : isString(input.name)
371
+ ? input.name
372
+ : "";
373
+ return {
374
+ rawName: toolName,
375
+ normalizedName: normalized,
376
+ category: "skill",
377
+ milestoneLine: skillName
378
+ ? `[step] Using skill: ${skillName}`
379
+ : "[step] Using skill...",
380
+ actionLabel: skillName
381
+ ? `Executing skill ${skillName}...`
382
+ : "Executing skill...",
383
+ details: { target: skillName || undefined },
384
+ };
385
+ }
386
+ // 7. Generic Fallback
387
+ return {
388
+ rawName: toolName,
389
+ normalizedName: normalized,
390
+ category: "other",
391
+ milestoneLine: `[step] Calling ${normalized}...`,
392
+ actionLabel: `Working on ${normalized}...`,
393
+ details: {},
394
+ };
395
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @302ai/media-studio-core
3
+ * Official client SDK and isomorphic core for 302 AI Media Studio.
4
+ */
5
+ export * from "./client/auth.js";
6
+ export * from "./client/chat.js";
7
+ export * from "./client/client.js";
8
+ export * from "./client/session.js";
9
+ export * from "./client/sessions.js";
10
+ export * from "./client/types.js";
11
+ export * from "./format/tool-formatter.js";
12
+ export * from "./media/media-result.js";
13
+ export * from "./sessions/types.js";
14
+ export * from "./stream/events.js";
15
+ export * from "./stream/stream.js";
16
+ export * from "./transport/errors.js";
17
+ export * from "./transport/types.js";
18
+ export * from "./transport/upstream.js";
package/dist/index.js ADDED
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @302ai/media-studio-core
3
+ * Official client SDK and isomorphic core for 302 AI Media Studio.
4
+ */
5
+ // Client Layer
6
+ export * from "./client/auth.js";
7
+ export * from "./client/chat.js";
8
+ export * from "./client/client.js";
9
+ export * from "./client/session.js";
10
+ export * from "./client/sessions.js";
11
+ export * from "./client/types.js";
12
+ // Format & Tool Milestone Layer
13
+ export * from "./format/tool-formatter.js";
14
+ // Media Domain Layer
15
+ export * from "./media/media-result.js";
16
+ // Sessions Layer
17
+ export * from "./sessions/types.js";
18
+ // Streaming & Events Layer
19
+ export * from "./stream/events.js";
20
+ export * from "./stream/stream.js";
21
+ export * from "./transport/errors.js";
22
+ // Transport Layer
23
+ export * from "./transport/types.js";
24
+ export * from "./transport/upstream.js";
@@ -0,0 +1,21 @@
1
+ import { z } from "zod";
2
+ export declare const MediaResultSchema: z.ZodObject<{
3
+ status: z.ZodOptional<z.ZodString>;
4
+ domain: z.ZodOptional<z.ZodString>;
5
+ operation: z.ZodOptional<z.ZodString>;
6
+ modelUsed: z.ZodOptional<z.ZodString>;
7
+ taskId: z.ZodOptional<z.ZodString>;
8
+ resultUrl: z.ZodOptional<z.ZodString>;
9
+ prompt: z.ZodOptional<z.ZodString>;
10
+ }, z.core.$strip>;
11
+ export type MediaResult = z.infer<typeof MediaResultSchema>;
12
+ /**
13
+ * Extracts a structured media result from a tool output string or object. Returns null unless
14
+ * the output contains media task completion identifiers (`task_id` or `result_url`).
15
+ */
16
+ export declare function extractMediaResult(output: unknown): MediaResult | null;
17
+ /**
18
+ * Merges an incoming MediaResult into an existing list of media results,
19
+ * deduplicating by resultUrl or taskId and enriching missing fields.
20
+ */
21
+ export declare function addOrMergeMediaResult(assets: MediaResult[], incoming: MediaResult): void;
@@ -0,0 +1,134 @@
1
+ import { isObject } from "es-toolkit/compat";
2
+ import { isString } from "es-toolkit/predicate";
3
+ import { z } from "zod";
4
+ export const MediaResultSchema = z.object({
5
+ status: z.string().optional(),
6
+ domain: z.string().optional(),
7
+ operation: z.string().optional(),
8
+ modelUsed: z.string().optional(),
9
+ taskId: z.string().optional(),
10
+ resultUrl: z.string().optional(),
11
+ prompt: z.string().optional(),
12
+ });
13
+ function parseKeyValueLines(output) {
14
+ const fields = new Map();
15
+ for (const line of output.split("\n")) {
16
+ const trimmed = line.trim().replace(/^- /, "").trim();
17
+ const eq = trimmed.indexOf("=");
18
+ if (eq <= 0)
19
+ continue;
20
+ const key = trimmed.slice(0, eq).trim();
21
+ let value = trimmed.slice(eq + 1).trim();
22
+ if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
23
+ value = value.slice(1, -1);
24
+ }
25
+ if (key)
26
+ fields.set(key, value);
27
+ }
28
+ return fields;
29
+ }
30
+ function isRecord(value) {
31
+ return isObject(value);
32
+ }
33
+ /**
34
+ * Extracts a structured media result from a tool output string or object. Returns null unless
35
+ * the output contains media task completion identifiers (`task_id` or `result_url`).
36
+ */
37
+ export function extractMediaResult(output) {
38
+ if (isRecord(output)) {
39
+ const target = isRecord(output.data) ? output.data : output;
40
+ const taskId = isString(target.task_id)
41
+ ? target.task_id
42
+ : isString(target.taskId)
43
+ ? target.taskId
44
+ : undefined;
45
+ const resultUrl = isString(target.result_url)
46
+ ? target.result_url
47
+ : isString(target.resultUrl)
48
+ ? target.resultUrl
49
+ : isString(target.url)
50
+ ? target.url
51
+ : isString(target.image_path)
52
+ ? target.image_path
53
+ : undefined;
54
+ if (!taskId && !resultUrl)
55
+ return null;
56
+ const domain = isString(target.domain)
57
+ ? target.domain
58
+ : isString(target.type)
59
+ ? target.type
60
+ : undefined;
61
+ return {
62
+ ...(isString(target.status) ? { status: target.status } : {}),
63
+ ...(domain ? { domain } : {}),
64
+ ...(isString(target.operation) ? { operation: target.operation } : {}),
65
+ ...(isString(target.model_used)
66
+ ? { modelUsed: target.model_used }
67
+ : isString(target.modelUsed)
68
+ ? { modelUsed: target.modelUsed }
69
+ : isString(target.model)
70
+ ? { modelUsed: target.model }
71
+ : {}),
72
+ ...(taskId ? { taskId } : {}),
73
+ ...(resultUrl ? { resultUrl } : {}),
74
+ ...(isString(target.prompt) ? { prompt: target.prompt } : {}),
75
+ };
76
+ }
77
+ if (!isString(output))
78
+ return null;
79
+ const fields = parseKeyValueLines(output);
80
+ const taskId = fields.get("task_id");
81
+ const resultUrl = fields.get("result_url") ?? fields.get("image_path");
82
+ if (!taskId && !resultUrl)
83
+ return null;
84
+ const status = fields.get("status");
85
+ const domain = fields.get("domain");
86
+ const operation = fields.get("operation");
87
+ const modelUsed = fields.get("model_used");
88
+ const prompt = fields.get("prompt");
89
+ return {
90
+ ...(status ? { status } : {}),
91
+ ...(domain ? { domain } : {}),
92
+ ...(operation ? { operation } : {}),
93
+ ...(modelUsed ? { modelUsed } : {}),
94
+ ...(taskId ? { taskId } : {}),
95
+ ...(resultUrl ? { resultUrl } : {}),
96
+ ...(prompt ? { prompt } : {}),
97
+ };
98
+ }
99
+ /**
100
+ * Merges an incoming MediaResult into an existing list of media results,
101
+ * deduplicating by resultUrl or taskId and enriching missing fields.
102
+ */
103
+ export function addOrMergeMediaResult(assets, incoming) {
104
+ const existing = assets.find((a) => (a.resultUrl &&
105
+ incoming.resultUrl &&
106
+ a.resultUrl === incoming.resultUrl) ||
107
+ (a.taskId && incoming.taskId && a.taskId === incoming.taskId));
108
+ if (existing) {
109
+ if (!existing.modelUsed && incoming.modelUsed) {
110
+ existing.modelUsed = incoming.modelUsed;
111
+ }
112
+ if (!existing.taskId && incoming.taskId) {
113
+ existing.taskId = incoming.taskId;
114
+ }
115
+ if (!existing.resultUrl && incoming.resultUrl) {
116
+ existing.resultUrl = incoming.resultUrl;
117
+ }
118
+ if (!existing.domain && incoming.domain) {
119
+ existing.domain = incoming.domain;
120
+ }
121
+ if (!existing.prompt && incoming.prompt) {
122
+ existing.prompt = incoming.prompt;
123
+ }
124
+ if (!existing.status && incoming.status) {
125
+ existing.status = incoming.status;
126
+ }
127
+ if (!existing.operation && incoming.operation) {
128
+ existing.operation = incoming.operation;
129
+ }
130
+ }
131
+ else {
132
+ assets.push({ ...incoming });
133
+ }
134
+ }
@@ -0,0 +1,16 @@
1
+ import { z } from "zod";
2
+ export declare const MediaResultSchema: z.ZodObject<{
3
+ status: z.ZodOptional<z.ZodString>;
4
+ domain: z.ZodOptional<z.ZodString>;
5
+ operation: z.ZodOptional<z.ZodString>;
6
+ modelUsed: z.ZodOptional<z.ZodString>;
7
+ taskId: z.ZodOptional<z.ZodString>;
8
+ resultUrl: z.ZodOptional<z.ZodString>;
9
+ prompt: z.ZodOptional<z.ZodString>;
10
+ }, z.core.$strip>;
11
+ export type MediaResult = z.infer<typeof MediaResultSchema>;
12
+ /**
13
+ * Extracts a structured media result from a tool output string. Returns null unless
14
+ * the output contains media task completion identifiers (`task_id` or `result_url`).
15
+ */
16
+ export declare function extractMediaResult(output: unknown): MediaResult | null;