@sync.so/mcp-server 0.1.3 → 0.2.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.
Files changed (261) hide show
  1. package/README.md +261 -86
  2. package/deploy/generation-status/app.html.gz +0 -0
  3. package/deploy/generation-status/release.json +6 -0
  4. package/dist/abort-signals.d.ts +6 -0
  5. package/dist/abort-signals.d.ts.map +1 -0
  6. package/dist/abort-signals.js +23 -0
  7. package/dist/account-features.d.ts +7 -0
  8. package/dist/account-features.d.ts.map +1 -0
  9. package/dist/account-features.js +48 -0
  10. package/dist/app-upload-relay.d.ts +13 -0
  11. package/dist/app-upload-relay.d.ts.map +1 -0
  12. package/dist/app-upload-relay.js +136 -0
  13. package/dist/asset-thumbnail.d.ts +3 -0
  14. package/dist/asset-thumbnail.d.ts.map +1 -0
  15. package/dist/asset-thumbnail.js +2 -0
  16. package/dist/auth/async-context.d.ts +5 -0
  17. package/dist/auth/async-context.d.ts.map +1 -0
  18. package/dist/auth/async-context.js +14 -0
  19. package/dist/auth/bearer-auth.d.ts +16 -0
  20. package/dist/auth/bearer-auth.d.ts.map +1 -0
  21. package/dist/auth/bearer-auth.js +55 -0
  22. package/dist/auth/client-credentials.d.ts +13 -0
  23. package/dist/auth/client-credentials.d.ts.map +1 -0
  24. package/dist/auth/client-credentials.js +67 -0
  25. package/dist/auth/device-auth.d.ts.map +1 -1
  26. package/dist/auth/device-auth.js +3 -2
  27. package/dist/auth/fixture-cleanup.d.ts +4 -0
  28. package/dist/auth/fixture-cleanup.d.ts.map +1 -0
  29. package/dist/auth/fixture-cleanup.js +9 -0
  30. package/dist/auth/index.d.ts +2 -0
  31. package/dist/auth/index.d.ts.map +1 -1
  32. package/dist/auth/index.js +2 -0
  33. package/dist/auth/oauth-diagnostics.d.ts +11 -0
  34. package/dist/auth/oauth-diagnostics.d.ts.map +1 -0
  35. package/dist/auth/oauth-diagnostics.js +78 -0
  36. package/dist/auth/oauth-provider.d.ts +16 -0
  37. package/dist/auth/oauth-provider.d.ts.map +1 -0
  38. package/dist/auth/oauth-provider.js +107 -0
  39. package/dist/auth/resource-metadata.d.ts +2 -0
  40. package/dist/auth/resource-metadata.d.ts.map +1 -0
  41. package/dist/auth/resource-metadata.js +3 -0
  42. package/dist/auth/token-verification.d.ts +11 -0
  43. package/dist/auth/token-verification.d.ts.map +1 -0
  44. package/dist/auth/token-verification.js +99 -0
  45. package/dist/auth/verification-cache.d.ts +8 -0
  46. package/dist/auth/verification-cache.d.ts.map +1 -0
  47. package/dist/auth/verification-cache.js +111 -0
  48. package/dist/chatgpt-analytics.d.ts +22 -0
  49. package/dist/chatgpt-analytics.d.ts.map +1 -0
  50. package/dist/chatgpt-analytics.js +172 -0
  51. package/dist/chatgpt-app.d.ts +15 -0
  52. package/dist/chatgpt-app.d.ts.map +1 -0
  53. package/dist/chatgpt-app.js +133 -0
  54. package/dist/chatgpt-release.d.ts +12 -0
  55. package/dist/chatgpt-release.d.ts.map +1 -0
  56. package/dist/chatgpt-release.js +30 -0
  57. package/dist/cli.js +30 -16
  58. package/dist/client-profile.d.ts +10 -0
  59. package/dist/client-profile.d.ts.map +1 -0
  60. package/dist/client-profile.js +45 -0
  61. package/dist/config.d.ts +17 -0
  62. package/dist/config.d.ts.map +1 -1
  63. package/dist/config.js +52 -0
  64. package/dist/generation-status-html.d.ts +2 -0
  65. package/dist/generation-status-html.d.ts.map +1 -0
  66. package/dist/generation-status-html.js +26 -0
  67. package/dist/generation-status.d.ts +6 -0
  68. package/dist/generation-status.d.ts.map +1 -0
  69. package/dist/generation-status.js +37 -0
  70. package/dist/http-client.d.ts +45 -1
  71. package/dist/http-client.d.ts.map +1 -1
  72. package/dist/http-client.js +171 -34
  73. package/dist/http-server.d.ts +53 -0
  74. package/dist/http-server.d.ts.map +1 -0
  75. package/dist/http-server.js +753 -0
  76. package/dist/http-session.d.ts +18 -0
  77. package/dist/http-session.d.ts.map +1 -0
  78. package/dist/http-session.js +189 -0
  79. package/dist/index.d.ts +4 -3
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +3 -2
  82. package/dist/oauth-response.d.ts +3 -0
  83. package/dist/oauth-response.d.ts.map +1 -0
  84. package/dist/oauth-response.js +30 -0
  85. package/dist/openapi/index.d.ts +2 -2
  86. package/dist/openapi/index.d.ts.map +1 -1
  87. package/dist/openapi/index.js +1 -1
  88. package/dist/openapi/parser.d.ts +2 -0
  89. package/dist/openapi/parser.d.ts.map +1 -1
  90. package/dist/openapi/parser.js +41 -1
  91. package/dist/openapi/types.d.ts +3 -0
  92. package/dist/openapi/types.d.ts.map +1 -1
  93. package/dist/organization-selection.d.ts +61 -0
  94. package/dist/organization-selection.d.ts.map +1 -0
  95. package/dist/organization-selection.js +142 -0
  96. package/dist/rate-limits.d.ts +13 -0
  97. package/dist/rate-limits.d.ts.map +1 -0
  98. package/dist/rate-limits.js +67 -0
  99. package/dist/revision.d.ts +6 -0
  100. package/dist/revision.d.ts.map +1 -0
  101. package/dist/revision.js +17 -0
  102. package/dist/runtime-diagnostics.d.ts +32 -0
  103. package/dist/runtime-diagnostics.d.ts.map +1 -0
  104. package/dist/runtime-diagnostics.js +58 -0
  105. package/dist/server.d.ts +23 -2
  106. package/dist/server.d.ts.map +1 -1
  107. package/dist/server.js +893 -32
  108. package/dist/session-registry.d.ts +54 -0
  109. package/dist/session-registry.d.ts.map +1 -0
  110. package/dist/session-registry.js +202 -0
  111. package/dist/session-telemetry.d.ts +13 -0
  112. package/dist/session-telemetry.d.ts.map +1 -0
  113. package/dist/session-telemetry.js +87 -0
  114. package/dist/spend-confirmation.d.ts +21 -0
  115. package/dist/spend-confirmation.d.ts.map +1 -0
  116. package/dist/spend-confirmation.js +105 -0
  117. package/dist/tool-analytics-context.d.ts +11 -0
  118. package/dist/tool-analytics-context.d.ts.map +1 -0
  119. package/dist/tool-analytics-context.js +8 -0
  120. package/dist/tool-names.d.ts +35 -0
  121. package/dist/tool-names.d.ts.map +1 -0
  122. package/dist/tool-names.js +133 -0
  123. package/dist/tools/account-context.d.ts +5 -0
  124. package/dist/tools/account-context.d.ts.map +1 -0
  125. package/dist/tools/account-context.js +131 -0
  126. package/dist/tools/account-management.d.ts +4 -0
  127. package/dist/tools/account-management.d.ts.map +1 -0
  128. package/dist/tools/account-management.js +111 -0
  129. package/dist/tools/app-tools.d.ts +31 -0
  130. package/dist/tools/app-tools.d.ts.map +1 -0
  131. package/dist/tools/app-tools.js +350 -0
  132. package/dist/tools/asset-analysis.d.ts +4 -0
  133. package/dist/tools/asset-analysis.d.ts.map +1 -0
  134. package/dist/tools/asset-analysis.js +172 -0
  135. package/dist/tools/asset-creators.d.ts +4 -0
  136. package/dist/tools/asset-creators.d.ts.map +1 -0
  137. package/dist/tools/asset-creators.js +38 -0
  138. package/dist/tools/audio-trim.d.ts +6 -0
  139. package/dist/tools/audio-trim.d.ts.map +1 -0
  140. package/dist/tools/audio-trim.js +79 -0
  141. package/dist/tools/batch-results.d.ts +16 -0
  142. package/dist/tools/batch-results.d.ts.map +1 -0
  143. package/dist/tools/batch-results.js +103 -0
  144. package/dist/tools/batches.d.ts +28 -0
  145. package/dist/tools/batches.d.ts.map +1 -0
  146. package/dist/tools/batches.js +293 -0
  147. package/dist/tools/dialogue.d.ts +6 -0
  148. package/dist/tools/dialogue.d.ts.map +1 -0
  149. package/dist/tools/dialogue.js +309 -0
  150. package/dist/tools/estimate-duration.d.ts +35 -0
  151. package/dist/tools/estimate-duration.d.ts.map +1 -0
  152. package/dist/tools/estimate-duration.js +66 -0
  153. package/dist/tools/feedback.d.ts +14 -0
  154. package/dist/tools/feedback.d.ts.map +1 -0
  155. package/dist/tools/feedback.js +72 -0
  156. package/dist/tools/generated-adapters.d.ts +25 -0
  157. package/dist/tools/generated-adapters.d.ts.map +1 -0
  158. package/dist/tools/generated-adapters.js +143 -0
  159. package/dist/tools/generation-context.d.ts +14 -0
  160. package/dist/tools/generation-context.d.ts.map +1 -0
  161. package/dist/tools/generation-context.js +39 -0
  162. package/dist/tools/generation-continuation.d.ts +4 -0
  163. package/dist/tools/generation-continuation.d.ts.map +1 -0
  164. package/dist/tools/generation-continuation.js +124 -0
  165. package/dist/tools/generation-costs.d.ts +6 -0
  166. package/dist/tools/generation-costs.d.ts.map +1 -0
  167. package/dist/tools/generation-costs.js +67 -0
  168. package/dist/tools/generation-output.d.ts +3 -0
  169. package/dist/tools/generation-output.d.ts.map +1 -0
  170. package/dist/tools/generation-output.js +39 -0
  171. package/dist/tools/generator.d.ts +35 -2
  172. package/dist/tools/generator.d.ts.map +1 -1
  173. package/dist/tools/generator.js +127 -9
  174. package/dist/tools/job-status.d.ts +35 -0
  175. package/dist/tools/job-status.d.ts.map +1 -0
  176. package/dist/tools/job-status.js +92 -0
  177. package/dist/tools/job-wait.d.ts +41 -0
  178. package/dist/tools/job-wait.d.ts.map +1 -0
  179. package/dist/tools/job-wait.js +84 -0
  180. package/dist/tools/jobs.d.ts +3 -0
  181. package/dist/tools/jobs.d.ts.map +1 -0
  182. package/dist/tools/jobs.js +210 -0
  183. package/dist/tools/media-upload.d.ts +27 -0
  184. package/dist/tools/media-upload.d.ts.map +1 -0
  185. package/dist/tools/media-upload.js +247 -0
  186. package/dist/tools/model-options.d.ts +36 -0
  187. package/dist/tools/model-options.d.ts.map +1 -0
  188. package/dist/tools/model-options.js +48 -0
  189. package/dist/tools/organization-choice.d.ts +9 -0
  190. package/dist/tools/organization-choice.d.ts.map +1 -0
  191. package/dist/tools/organization-choice.js +136 -0
  192. package/dist/tools/organizations.d.ts +56 -0
  193. package/dist/tools/organizations.d.ts.map +1 -0
  194. package/dist/tools/organizations.js +235 -0
  195. package/dist/tools/output-schemas.d.ts +54 -0
  196. package/dist/tools/output-schemas.d.ts.map +1 -0
  197. package/dist/tools/output-schemas.js +58 -0
  198. package/dist/tools/overrides.d.ts +3 -0
  199. package/dist/tools/overrides.d.ts.map +1 -1
  200. package/dist/tools/overrides.js +113 -15
  201. package/dist/tools/playback-metadata.d.ts +4 -0
  202. package/dist/tools/playback-metadata.d.ts.map +1 -0
  203. package/dist/tools/playback-metadata.js +51 -0
  204. package/dist/tools/project-destination.d.ts +13 -0
  205. package/dist/tools/project-destination.d.ts.map +1 -0
  206. package/dist/tools/project-destination.js +45 -0
  207. package/dist/tools/project-history.d.ts +6 -0
  208. package/dist/tools/project-history.d.ts.map +1 -0
  209. package/dist/tools/project-history.js +45 -0
  210. package/dist/tools/project-intent.d.ts +8 -0
  211. package/dist/tools/project-intent.d.ts.map +1 -0
  212. package/dist/tools/project-intent.js +87 -0
  213. package/dist/tools/running-submissions.d.ts +7 -0
  214. package/dist/tools/running-submissions.d.ts.map +1 -0
  215. package/dist/tools/running-submissions.js +91 -0
  216. package/dist/tools/speech.d.ts +6 -0
  217. package/dist/tools/speech.d.ts.map +1 -0
  218. package/dist/tools/speech.js +116 -0
  219. package/dist/tools/studio-draft.d.ts +7 -0
  220. package/dist/tools/studio-draft.d.ts.map +1 -0
  221. package/dist/tools/studio-draft.js +195 -0
  222. package/dist/tools/studio-view-contract.d.ts +184 -0
  223. package/dist/tools/studio-view-contract.d.ts.map +1 -0
  224. package/dist/tools/studio-view-contract.js +67 -0
  225. package/dist/tools/studio-view.d.ts +6 -0
  226. package/dist/tools/studio-view.d.ts.map +1 -0
  227. package/dist/tools/studio-view.js +126 -0
  228. package/dist/tools/timeline-lipsync.d.ts +316 -0
  229. package/dist/tools/timeline-lipsync.d.ts.map +1 -0
  230. package/dist/tools/timeline-lipsync.js +304 -0
  231. package/dist/tools/translation-preview.d.ts +8 -0
  232. package/dist/tools/translation-preview.d.ts.map +1 -0
  233. package/dist/tools/translation-preview.js +146 -0
  234. package/dist/tools/translation.d.ts +6 -0
  235. package/dist/tools/translation.d.ts.map +1 -0
  236. package/dist/tools/translation.js +131 -0
  237. package/dist/tools/upload-widget.d.ts +13 -0
  238. package/dist/tools/upload-widget.d.ts.map +1 -0
  239. package/dist/tools/upload-widget.js +788 -0
  240. package/dist/toolsets.d.ts +18 -0
  241. package/dist/toolsets.d.ts.map +1 -0
  242. package/dist/toolsets.js +157 -0
  243. package/dist/update-check.d.ts +38 -0
  244. package/dist/update-check.d.ts.map +1 -0
  245. package/dist/update-check.js +150 -0
  246. package/dist/upload-runtime.d.ts +45 -0
  247. package/dist/upload-runtime.d.ts.map +1 -0
  248. package/dist/upload-runtime.js +156 -0
  249. package/dist/upload-source.d.ts +4 -0
  250. package/dist/upload-source.d.ts.map +1 -0
  251. package/dist/upload-source.js +46 -0
  252. package/dist/version.d.ts +2 -0
  253. package/dist/version.d.ts.map +1 -0
  254. package/dist/version.js +3 -0
  255. package/package.json +79 -33
  256. package/dist/openapi/parser.test.d.ts +0 -2
  257. package/dist/openapi/parser.test.d.ts.map +0 -1
  258. package/dist/openapi/parser.test.js +0 -188
  259. package/dist/tools/generator.test.d.ts +0 -2
  260. package/dist/tools/generator.test.d.ts.map +0 -1
  261. package/dist/tools/generator.test.js +0 -178
@@ -0,0 +1,79 @@
1
+ import { z } from 'zod';
2
+ import { schemaProperty } from '../openapi/parser.js';
3
+ export function supportsAudioTrimming(operations) {
4
+ const create = operations.find((op) => op.method === 'post' && op.path === '/v2/audio-trims');
5
+ return (!!create?.parameters.some((p) => p.in === 'header' && p.name.toLowerCase() === 'idempotency-key' && p.required) &&
6
+ schemaProperty(create.requestBody?.schema ?? {}, 'assetId')?.format === 'uuid' &&
7
+ !!operations.some((op) => op.method === 'get' && op.path === '/v2/audio-trims/{id}'));
8
+ }
9
+ const result = z.object({
10
+ id: z.uuid(),
11
+ status: z.enum(['queued', 'processing', 'completed', 'failed']),
12
+ assetId: z.uuid().nullable(),
13
+ durationSeconds: z.number().positive().nullable(),
14
+ errorCode: z.string().nullable(),
15
+ retryAfterMs: z.number().int().nonnegative(),
16
+ });
17
+ export function createAudioTrimTools(operations, http) {
18
+ if (!supportsAudioTrimming(operations))
19
+ return [];
20
+ // assetId is the API's name for the source; audioAssetId is the older MCP name.
21
+ const create = z
22
+ .object({
23
+ assetId: z.uuid().optional().describe('Saved AUDIO asset to shorten.'),
24
+ projectId: z.uuid().optional(),
25
+ durationSeconds: z.number().min(0.01).max(3600),
26
+ idempotencyKey: z.uuid(),
27
+ audioAssetId: z
28
+ .uuid()
29
+ .optional()
30
+ .describe('Deprecated alias of assetId.')
31
+ .meta({ deprecated: true }),
32
+ })
33
+ .strict();
34
+ const read = z.object({ id: z.uuid() }).strict();
35
+ return [
36
+ {
37
+ name: 'audio-trims_create',
38
+ title: 'Prepare shorter audio',
39
+ description: 'Save the first durationSeconds of an authorized Sync audio asset, passed as assetId, as a new reusable asset. Explain and confirm the partial duration first. Persist one idempotencyKey and reuse it with identical inputs after an uncertain response. This prepares audio only: it never generates video, spends generation credits, or consumes a free generation. Poll audio-trims_get until completed, then use its assetId and actual duration for a fresh quote. Partial video lipsync must use cut_off; keep the original draft unchanged. Failed operations require an explicit new attempt.',
40
+ inputSchema: create.shape,
41
+ outputSchema: result.shape,
42
+ annotations: {
43
+ readOnlyHint: false,
44
+ destructiveHint: false,
45
+ idempotentHint: true,
46
+ openWorldHint: false,
47
+ },
48
+ handler: async (args, context) => {
49
+ const { assetId, audioAssetId, idempotencyKey, ...rest } = create.parse(args);
50
+ const source = assetId ?? audioAssetId;
51
+ if (source === undefined)
52
+ throw new Error('Pass assetId.');
53
+ if (assetId !== undefined && audioAssetId !== undefined && assetId !== audioAssetId)
54
+ throw new Error('assetId and audioAssetId name different assets; pass assetId only.');
55
+ return result.parse(await http.request('post', '/v2/audio-trims', {
56
+ body: { ...rest, assetId: source },
57
+ headers: { 'Idempotency-Key': idempotencyKey },
58
+ signal: context?.signal,
59
+ }));
60
+ },
61
+ },
62
+ {
63
+ name: 'audio-trims_get',
64
+ title: 'Read audio preparation',
65
+ description: 'Read the same authorized audio preparation. Pending status is resumable; do not create another trim. This never starts video generation.',
66
+ inputSchema: read.shape,
67
+ outputSchema: result.shape,
68
+ annotations: {
69
+ readOnlyHint: true,
70
+ destructiveHint: false,
71
+ idempotentHint: true,
72
+ openWorldHint: false,
73
+ },
74
+ handler: async (args, context) => result.parse(await http.request('get', `/v2/audio-trims/${read.parse(args).id}`, {
75
+ signal: context?.signal,
76
+ })),
77
+ },
78
+ ];
79
+ }
@@ -0,0 +1,16 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * A finished batch's results. The API writes one JSONL line per item (request_id, status,
4
+ * error and the /v2/generate response) and serves the file publicly behind the API origin;
5
+ * batches_get turns it into rows the agent can use without downloading anything.
6
+ */
7
+ export declare const BATCH_DONE: Set<string>;
8
+ export declare const resultRow: z.ZodObject<{
9
+ requestId: z.ZodString;
10
+ status: z.ZodString;
11
+ generationId: z.ZodOptional<z.ZodString>;
12
+ outputMediaUrl: z.ZodOptional<z.ZodString>;
13
+ error: z.ZodOptional<z.ZodUnknown>;
14
+ }, z.core.$strip>;
15
+ export declare function withResults(batch: Record<string, unknown>, which: 'all' | 'failed' | 'none', apiBaseUrl: string, signal: AbortSignal | undefined): Promise<Record<string, unknown>>;
16
+ //# sourceMappingURL=batch-results.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batch-results.d.ts","sourceRoot":"","sources":["../../src/tools/batch-results.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;GAIG;AAEH,eAAO,MAAM,UAAU,aAAmC,CAAC;AAQ3D,eAAO,MAAM,SAAS;;;;;;iBAMpB,CAAC;AAsEH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,KAAK,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,EAChC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,WAAW,GAAG,SAAS,GAC9B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAiBlC"}
@@ -0,0 +1,103 @@
1
+ import { z } from 'zod';
2
+ import { boundedBy, TIMED_OUT } from './job-wait.js';
3
+ /**
4
+ * A finished batch's results. The API writes one JSONL line per item (request_id, status,
5
+ * error and the /v2/generate response) and serves the file publicly behind the API origin;
6
+ * batches_get turns it into rows the agent can use without downloading anything.
7
+ */
8
+ export const BATCH_DONE = new Set(['COMPLETED', 'FAILED']);
9
+ const RESULTS_TIMEOUT_MS = 30_000;
10
+ const MAX_RESULTS_BYTES = 20 * 1024 * 1024;
11
+ function isRecord(value) {
12
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
13
+ }
14
+ export const resultRow = z.object({
15
+ requestId: z.string(),
16
+ status: z.string().describe('The generation status: COMPLETED, FAILED or REJECTED.'),
17
+ generationId: z.string().optional().describe('Read it with generations_get.'),
18
+ outputMediaUrl: z.string().optional().describe('The finished video; return it verbatim.'),
19
+ error: z.unknown().optional(),
20
+ });
21
+ function rowOf(line) {
22
+ if (!isRecord(line) || typeof line.request_id !== 'string')
23
+ return undefined;
24
+ const response = isRecord(line.response) ? line.response : {};
25
+ const output = response.outputMediaUrl ?? response.outputUrl;
26
+ return {
27
+ requestId: line.request_id,
28
+ status: typeof line.status === 'string' ? line.status : 'UNKNOWN',
29
+ ...(typeof response.id === 'string' ? { generationId: response.id } : {}),
30
+ ...(typeof output === 'string' && output ? { outputMediaUrl: output } : {}),
31
+ ...(line.error === null || line.error === undefined ? {} : { error: line.error }),
32
+ };
33
+ }
34
+ const TOO_LARGE = 'the result file is too large to read here';
35
+ /**
36
+ * The body as text, counting decoded bytes as they arrive, so a chunked or compressed
37
+ * file cannot grow past the cap in memory: the download is canceled once it would.
38
+ */
39
+ async function readCapped(res) {
40
+ if (!res.body)
41
+ return '';
42
+ const reader = res.body.getReader();
43
+ const chunks = [];
44
+ let bytes = 0;
45
+ for (;;) {
46
+ const { done, value } = await reader.read();
47
+ if (done)
48
+ break;
49
+ bytes += value.byteLength;
50
+ if (bytes > MAX_RESULTS_BYTES) {
51
+ await reader.cancel().catch(() => { });
52
+ throw new Error(TOO_LARGE);
53
+ }
54
+ chunks.push(value);
55
+ }
56
+ return Buffer.concat(chunks).toString('utf8');
57
+ }
58
+ /**
59
+ * The result file is public behind the API origin, which redirects to a signed copy; it
60
+ * is fetched without credentials, and only from the API origin.
61
+ */
62
+ async function readResults(outputUrl, apiBaseUrl, signal) {
63
+ const url = new URL(outputUrl);
64
+ if (url.origin !== new URL(apiBaseUrl).origin)
65
+ throw new Error(`the result file is not on the Sync API (${url.origin})`);
66
+ const response = await boundedBy(RESULTS_TIMEOUT_MS, signal, (bounded) => fetch(url, { signal: bounded, redirect: 'follow' }).then(async (res) => {
67
+ if (!res.ok)
68
+ throw new Error(`the result file answered ${res.status}`);
69
+ if (Number(res.headers.get('content-length') ?? 0) > MAX_RESULTS_BYTES)
70
+ throw new Error(TOO_LARGE);
71
+ return readCapped(res);
72
+ }));
73
+ if (response === TIMED_OUT)
74
+ throw new Error('reading the result file timed out');
75
+ return response
76
+ .split('\n')
77
+ .filter((line) => line.trim() !== '')
78
+ .map((line) => rowOf(JSON.parse(line)))
79
+ .filter((row) => row !== undefined);
80
+ }
81
+ const FAILED_ROW = new Set(['FAILED', 'REJECTED']);
82
+ export async function withResults(batch, which, apiBaseUrl, signal) {
83
+ if (which === 'none' || !BATCH_DONE.has(String(batch.status)))
84
+ return batch;
85
+ if (typeof batch.outputUrl !== 'string' || batch.outputUrl === '')
86
+ return batch;
87
+ try {
88
+ const rows = await readResults(batch.outputUrl, apiBaseUrl, signal);
89
+ return {
90
+ ...batch,
91
+ results: which === 'failed' ? rows.filter((row) => FAILED_ROW.has(row.status)) : rows,
92
+ };
93
+ }
94
+ catch (error) {
95
+ if (signal?.aborted)
96
+ throw error;
97
+ const reason = error instanceof Error ? error.message : String(error);
98
+ return {
99
+ ...batch,
100
+ resultsError: `${reason}. Download outputUrl (JSONL, one line per item with request_id, status, error and response) or find the generations with generations_list.`,
101
+ };
102
+ }
103
+ }
@@ -0,0 +1,28 @@
1
+ import { z } from 'zod';
2
+ import { type HttpClient } from '../http-client.js';
3
+ import type { ParsedOperation } from '../openapi/types.js';
4
+ import type { McpToolDefinition } from './generator.js';
5
+ /**
6
+ * batches_create, batches_get and batches_list: many independent /v2/generate generations
7
+ * as one Sync batch. batches_create builds the API's JSON `rows` form and checks locally
8
+ * what the API would refuse anyway, naming the item. batches_get adds each row's result
9
+ * from batch-results.ts once the batch finishes.
10
+ */
11
+ export declare const BATCH_TOOLS: readonly ['batches_create', 'batches_get', 'batches_list'];
12
+ export declare function isBatchOperation(operation: ParsedOperation): boolean;
13
+ declare const item: z.ZodObject<{
14
+ requestId: z.ZodOptional<z.ZodString>;
15
+ payload: z.ZodRecord<z.ZodString, z.ZodUnknown>;
16
+ }, z.core.$strict>;
17
+ type BatchRow = {
18
+ request_id: string;
19
+ endpoint: '/v2/generate';
20
+ payload: Record<string, unknown>;
21
+ };
22
+ /** The API's rows, or an error naming every item it would refuse (up to a few). */
23
+ export declare function buildBatchRows(items: z.infer<typeof item>[]): BatchRow[];
24
+ /** Running share of the batch, for progress notifications. */
25
+ export declare function batchProgress(batch: Record<string, unknown>): number | undefined;
26
+ export declare function createBatchTools(operations: ParsedOperation[], http: HttpClient, apiBaseUrl: string): McpToolDefinition[];
27
+ export {};
28
+ //# sourceMappingURL=batches.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batches.d.ts","sourceRoot":"","sources":["../../src/tools/batches.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAmB,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,OAAO,KAAK,EAAE,iBAAiB,EAAsB,MAAM,gBAAgB,CAAC;AAI5E;;;;;GAKG;AAEH,eAAO,MAAM,WAAW,YAAI,gBAAgB,EAAE,aAAa,EAAE,cAAc,CAAU,CAAC;AAUtF,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,eAAe,GAAG,OAAO,CAEpE;AAYD,QAAA,MAAM,IAAI;;;kBAgBC,CAAC;AAqIZ,KAAK,QAAQ,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC;AAEnG,mFAAmF;AACnF,wBAAgB,cAAc,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,EAAE,GAAG,QAAQ,EAAE,CA8BxE;AA6BD,8DAA8D;AAC9D,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,SAAS,CAKhF;AAED,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,eAAe,EAAE,EAC7B,IAAI,EAAE,UAAU,EAChB,UAAU,EAAE,MAAM,GACjB,iBAAiB,EAAE,CAkGrB"}
@@ -0,0 +1,293 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { z } from 'zod';
3
+ import { ApiRequestError } from '../http-client.js';
4
+ import { BATCH_DONE, resultRow, withResults } from './batch-results.js';
5
+ import { pollUntil, waitInput } from './job-wait.js';
6
+ import { projectIntentError, projectUseAllowed } from './project-intent.js';
7
+ /**
8
+ * batches_create, batches_get and batches_list: many independent /v2/generate generations
9
+ * as one Sync batch. batches_create builds the API's JSON `rows` form and checks locally
10
+ * what the API would refuse anyway, naming the item. batches_get adds each row's result
11
+ * from batch-results.ts once the batch finishes.
12
+ */
13
+ export const BATCH_TOOLS = ['batches_create', 'batches_get', 'batches_list'];
14
+ const MIN_ITEMS = 20;
15
+ // The API measures the rows as JSONL, one compact JSON object per line.
16
+ const MAX_JSONL_BYTES = 5 * 1024 * 1024;
17
+ const LIST_STATUSES = ['PENDING', 'JOBS_CREATED', 'PROCESSING', 'COMPLETED', 'FAILED'];
18
+ const MAX_REPORTED_PROBLEMS = 10;
19
+ const BATCH_ROUTES = ['post /v2/batch', 'get /v2/batch', 'get /v2/batch/{id}'];
20
+ export function isBatchOperation(operation) {
21
+ return BATCH_ROUTES.includes(`${operation.method} ${operation.path}`);
22
+ }
23
+ function supportsBatches(operations) {
24
+ return BATCH_ROUTES.every((route) => operations.some((operation) => `${operation.method} ${operation.path}` === route));
25
+ }
26
+ function isRecord(value) {
27
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
28
+ }
29
+ const item = z
30
+ .object({
31
+ requestId: z
32
+ .string()
33
+ .trim()
34
+ .min(1)
35
+ .optional()
36
+ .describe('Your id for this item, unique in the batch. Omit it to get item-<n> (n counts from 1). Each result row carries it.'),
37
+ payload: z
38
+ .record(z.string(), z.unknown())
39
+ .describe('One POST /v2/generate body, the same as a single generation: {model, input: [{type: "video", url}, {type: "audio", url}], options?}. Input items take a public URL or a Sync assetId; a text input takes {type: "text", provider: {...}} with a voice from voices_list.'),
40
+ })
41
+ .strict();
42
+ const createInput = z
43
+ .object({
44
+ items: z
45
+ .array(item)
46
+ .min(MIN_ITEMS, `A batch needs at least ${MIN_ITEMS} items; create fewer generations one at a time.`)
47
+ .describe(`The generations, in order; at least ${MIN_ITEMS}. Plans allow up to 500 per batch by default, and the whole batch must fit in 5 MB as JSONL.`),
48
+ dryRun: z
49
+ .boolean()
50
+ .optional()
51
+ .describe('Validate every item with the API without creating the batch or spending anything; answers status VALIDATED.'),
52
+ webhookUrl: z
53
+ .url()
54
+ .optional()
55
+ .describe('URL Sync POSTs the batch to when it finishes, signed with the organization webhook secret.'),
56
+ projectIntent: z
57
+ .literal('explicit')
58
+ .optional()
59
+ .describe('Required when any payload sets projectId. Set only when the user explicitly chose that project, or it is the project just created for this request; never infer it from a matching name or an open editor.'),
60
+ })
61
+ .strict();
62
+ /** Items writing into an existing project need the same explicit intent as every write tool. */
63
+ function assertProjectIntent(items, projectIntent) {
64
+ const refused = items.flatMap(({ payload }, index) => projectUseAllowed(payload.projectId, projectIntent) ? [] : [`items[${index}]`]);
65
+ if (refused.length === 0)
66
+ return;
67
+ const named = refused.slice(0, MAX_REPORTED_PROBLEMS).join(', ');
68
+ const more = refused.length > MAX_REPORTED_PROBLEMS
69
+ ? ` and ${refused.length - MAX_REPORTED_PROBLEMS} more`
70
+ : '';
71
+ throw projectIntentError(`The batch was not sent: ${named}${more} set payload.projectId without projectIntent=explicit. `);
72
+ }
73
+ const metrics = z
74
+ .looseObject({
75
+ total_generations: z.number().optional(),
76
+ success_count: z.number().optional(),
77
+ failed_count: z.number().optional(),
78
+ pending_count: z.number().optional(),
79
+ })
80
+ .describe('Generation counts: total, succeeded, failed and still pending.');
81
+ const batchFields = {
82
+ id: z.string().describe('The batch id; pass it to batches_get.'),
83
+ status: z
84
+ .string()
85
+ .describe('PENDING, JOBS_CREATED or PROCESSING while running (internal states also mean running); COMPLETED or FAILED when done.'),
86
+ created_at: z.string().optional(),
87
+ metrics: metrics.optional(),
88
+ webhookUrl: z.string().nullable().optional(),
89
+ outputUrl: z
90
+ .string()
91
+ .nullable()
92
+ .optional()
93
+ .describe('The JSONL result file once the batch finishes; empty while it runs.'),
94
+ };
95
+ const createOutput = {
96
+ ...batchFields,
97
+ id: batchFields.id.optional(),
98
+ requestIds: z
99
+ .array(z.string())
100
+ .describe('Each item’s requestId, in item order, including generated ones.'),
101
+ };
102
+ const getInput = z
103
+ .object({
104
+ id: z.string().trim().min(1).describe('The batch id batches_create or batches_list returned.'),
105
+ wait: waitInput,
106
+ results: z
107
+ .enum(['all', 'failed', 'none'])
108
+ .optional()
109
+ .describe('Once the batch finishes, which result rows to return: all (default), failed (FAILED or REJECTED only) or none.'),
110
+ })
111
+ .strict();
112
+ const getOutput = {
113
+ ...batchFields,
114
+ results: z
115
+ .array(resultRow)
116
+ .optional()
117
+ .describe('One row per item from the result file, once the batch finishes.'),
118
+ resultsError: z
119
+ .string()
120
+ .optional()
121
+ .describe('Why the result file could not be read; the batch fields are still current.'),
122
+ };
123
+ const listInput = z
124
+ .object({
125
+ status: z.enum(LIST_STATUSES).optional(),
126
+ createdAfter: z.string().optional().describe('ISO 8601 date-time.'),
127
+ createdBefore: z.string().optional().describe('ISO 8601 date-time.'),
128
+ })
129
+ .strict();
130
+ function payloadProblem(payload) {
131
+ // The API expands Edit Dialogue rows itself, with their own checks.
132
+ if (payload.dialogueEdit !== undefined)
133
+ return undefined;
134
+ if (typeof payload.model !== 'string' || payload.model.trim() === '')
135
+ return 'payload.model must name a model, such as sync-3 (see models_list)';
136
+ if (!Array.isArray(payload.input) || payload.input.length === 0)
137
+ return 'payload.input must list the inputs, such as {type: "video", url} and {type: "audio", url}';
138
+ const bad = payload.input.findIndex((entry) => !isRecord(entry) || typeof entry.type !== 'string');
139
+ return bad === -1 ? undefined : `payload.input[${bad}] needs a type: video, image, audio or text`;
140
+ }
141
+ /** The API's rows, or an error naming every item it would refuse (up to a few). */
142
+ export function buildBatchRows(items) {
143
+ const problems = [];
144
+ const firstUse = new Map();
145
+ items.forEach(({ requestId, payload }, index) => {
146
+ if (requestId !== undefined) {
147
+ const first = firstUse.get(requestId);
148
+ if (first === undefined)
149
+ firstUse.set(requestId, index);
150
+ else
151
+ problems.push(`items[${index}].requestId "${requestId}" repeats items[${first}]`);
152
+ }
153
+ const problem = payloadProblem(payload);
154
+ if (problem)
155
+ problems.push(`items[${index}]: ${problem}`);
156
+ });
157
+ if (problems.length > 0) {
158
+ const more = problems.length - MAX_REPORTED_PROBLEMS;
159
+ throw new Error(`The batch was not sent. ${problems.slice(0, MAX_REPORTED_PROBLEMS).join('; ')}${more > 0 ? `; and ${more} more` : ''}. Every requestId must be unique in the batch.`);
160
+ }
161
+ const rows = items.map(({ requestId, payload }, index) => {
162
+ let id = requestId ?? `item-${index + 1}`;
163
+ if (requestId === undefined && firstUse.has(id))
164
+ id = `${id}-${randomUUID().slice(0, 8)}`;
165
+ firstUse.set(id, index);
166
+ return { request_id: id, endpoint: '/v2/generate', payload };
167
+ });
168
+ const bytes = Buffer.byteLength(rows.map((row) => JSON.stringify(row)).join('\n'));
169
+ if (bytes > MAX_JSONL_BYTES)
170
+ throw new Error(`The batch was not sent: it is ${(bytes / 1024 / 1024).toFixed(1)} MB as JSONL and the API takes at most 5 MB. Split the items into smaller batches.`);
171
+ return rows;
172
+ }
173
+ const BATCH_LIMIT_STEP = 'This account already runs as many batches as its plan allows (activeBatches of concurrencyLimit). Wait for one to finish, with batches_list status PROCESSING or batches_get wait: true, then create this batch once. Creating it again before then fails the same way.';
174
+ function planStep(requiredPlan) {
175
+ const plan = requiredPlan === 'enterprise'
176
+ ? ' No self-serve plan includes it; the user can contact sync.'
177
+ : requiredPlan
178
+ ? ` It needs the ${requiredPlan} plan or above.`
179
+ : '';
180
+ return `Batch is not on this account’s plan.${plan} Tell the user, with billingUrl when present, where a person can change the plan. Meanwhile, create the generations one at a time with lipsync_create or dubbing_create. Creating the batch again fails the same way until the plan changes.`;
181
+ }
182
+ /** Tell the agent what changes the outcome; API lines are 1-based item positions. */
183
+ function explainCreateError(error) {
184
+ if (!(error instanceof ApiRequestError))
185
+ throw error;
186
+ if (error.status === 402 && error.code === 'plan_feature_unavailable')
187
+ error.nextStep = planStep(error.details?.requiredPlan);
188
+ else if (error.status === 429 && error.code === 'concurrency_limit_reached')
189
+ error.nextStep = BATCH_LIMIT_STEP;
190
+ else if (error.status === 400 || error.status === 422) {
191
+ const line = error.details?.lineNumber;
192
+ error.nextStep = `${line ? `Line ${line} is items[${line - 1}]. ` : 'An API message citing line N means items[N-1]. '}Fix the items, check with dryRun: true, then create the batch once.`;
193
+ }
194
+ throw error;
195
+ }
196
+ /** Running share of the batch, for progress notifications. */
197
+ export function batchProgress(batch) {
198
+ const counts = isRecord(batch.metrics) ? batch.metrics : {};
199
+ const total = Number(counts.total_generations);
200
+ const done = Number(counts.success_count ?? 0) + Number(counts.failed_count ?? 0);
201
+ return total > 0 && Number.isFinite(done) ? Math.min((done / total) * 100, 100) : undefined;
202
+ }
203
+ export function createBatchTools(operations, http, apiBaseUrl) {
204
+ if (!supportsBatches(operations))
205
+ return [];
206
+ const readBatch = async (id, signal) => {
207
+ const batch = await http.request('get', `/v2/batch/${encodeURIComponent(id)}`, { signal });
208
+ return isRecord(batch) ? batch : { id, status: 'UNKNOWN' };
209
+ };
210
+ const get = async (args, context) => {
211
+ const input = getInput.parse(args);
212
+ const batch = input.wait
213
+ ? await pollUntil((signal) => readBatch(input.id, signal), (b) => BATCH_DONE.has(String(b.status)), context, {
214
+ describe: (b) => {
215
+ const progress = batchProgress(b);
216
+ return `batch ${String(b.status).toLowerCase()}${progress === undefined ? '' : ` ${Math.round(progress)}%`}`;
217
+ },
218
+ })
219
+ : await readBatch(input.id, context?.signal);
220
+ return withResults(batch, input.results ?? 'all', apiBaseUrl, context?.signal);
221
+ };
222
+ return [
223
+ {
224
+ name: 'batches_create',
225
+ title: 'Create a batch of generations',
226
+ description: `Submit many independent lipsync generations at once as one Sync batch, such as localizing one campaign video into many languages or syncing many videos to their own audio. Each item is one /v2/generate body; Sync queues them, so they skip the per-generation concurrency limit, and results can take up to a day. Use it for ${MIN_ITEMS} or more generations that do not depend on each other; for fewer, or when one result feeds the next, use lipsync_create or dubbing_create per video. Call with dryRun: true first to have the API check every item without creating anything. This is a paid action: every item is billed as its own generation. Before creating, estimate a representative item with generations_estimate-cost, tell the user the item count and the estimated total, and wait for their confirmation. Not idempotent: each call creates a new batch, so after an uncertain response check batches_list before creating again. Leave payload.projectId out for new work; a payload with projectId needs projectIntent=explicit, which only an explicit user selection or a project just created for this request allows. Batch may not be on every plan: a 402 names requiredPlan and billingUrl. Returns the batch id and each item's requestId; then call batches_get with wait: true.`,
227
+ inputSchema: createInput.shape,
228
+ outputSchema: createOutput,
229
+ annotations: {
230
+ readOnlyHint: false,
231
+ destructiveHint: false,
232
+ idempotentHint: false,
233
+ openWorldHint: true,
234
+ },
235
+ handler: async (args, context) => {
236
+ const { items, dryRun, webhookUrl, projectIntent } = createInput.parse(args);
237
+ assertProjectIntent(items, projectIntent);
238
+ const rows = buildBatchRows(items);
239
+ const response = await http
240
+ .request('post', '/v2/batch', {
241
+ body: {
242
+ rows,
243
+ ...(dryRun ? { dry_run: true } : {}),
244
+ ...(webhookUrl ? { webhook_url: webhookUrl } : {}),
245
+ },
246
+ signal: context?.signal,
247
+ })
248
+ .catch(explainCreateError);
249
+ return {
250
+ ...(isRecord(response) ? response : {}),
251
+ requestIds: rows.map((row) => row.request_id),
252
+ };
253
+ },
254
+ },
255
+ {
256
+ name: 'batches_get',
257
+ title: 'Read a batch',
258
+ description: 'Read a Sync batch: status, metrics (total, succeeded, failed, pending) and outputUrl. Once it is COMPLETED or FAILED, results lists each item by requestId with its generation status, generationId, outputMediaUrl or error. With wait: true it waits on the server for up to about 50 seconds and sends progress notifications; batches can take hours, so call again while it runs and never create the batch again.',
259
+ inputSchema: getInput.shape,
260
+ outputSchema: getOutput,
261
+ annotations: {
262
+ readOnlyHint: true,
263
+ destructiveHint: false,
264
+ idempotentHint: true,
265
+ openWorldHint: false,
266
+ },
267
+ handler: get,
268
+ },
269
+ {
270
+ name: 'batches_list',
271
+ title: 'List batches',
272
+ description: 'List this account’s Sync batches with their status and metrics, optionally by status or creation time. Use it to find a batch id, to check for a batch just created after an uncertain batches_create response, or to see which batches are still running.',
273
+ inputSchema: listInput.shape,
274
+ outputSchema: { batches: z.array(z.looseObject(batchFields)) },
275
+ annotations: {
276
+ readOnlyHint: true,
277
+ destructiveHint: false,
278
+ idempotentHint: true,
279
+ openWorldHint: false,
280
+ },
281
+ handler: async (args, context) => {
282
+ const { status, createdAfter, createdBefore } = listInput.parse(args);
283
+ const query = Object.fromEntries(Object.entries({
284
+ status,
285
+ created_after: createdAfter,
286
+ created_before: createdBefore,
287
+ }).filter((entry) => entry[1] !== undefined));
288
+ const batches = await http.request('get', '/v2/batch', { query, signal: context?.signal });
289
+ return { batches: Array.isArray(batches) ? batches : [] };
290
+ },
291
+ },
292
+ ];
293
+ }
@@ -0,0 +1,6 @@
1
+ import { type HttpClient } from '../http-client.js';
2
+ import type { ParsedOperation } from '../openapi/types.js';
3
+ import { type McpToolDefinition } from './generator.js';
4
+ export declare function dialogueOperations(operations: ParsedOperation[]): ParsedOperation[];
5
+ export declare function createDialogueTools(operations: ParsedOperation[], httpClient: HttpClient): McpToolDefinition[];
6
+ //# sourceMappingURL=dialogue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialogue.d.ts","sourceRoot":"","sources":["../../src/tools/dialogue.ts"],"names":[],"mappings":"AACA,OAAO,EAAmB,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAG3D,OAAO,EAAiB,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAWvE,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,eAAe,EAAE,qBAE/D;AA2CD,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,eAAe,EAAE,EAC7B,UAAU,EAAE,UAAU,GACrB,iBAAiB,EAAE,CAmUrB"}