@se-studio/contentful-cms 3.2.0 → 3.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.
Files changed (74) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/cli/program.d.ts.map +1 -1
  3. package/dist/cli/program.js +2 -0
  4. package/dist/cli/program.js.map +1 -1
  5. package/dist/commands/media.d.ts +4 -0
  6. package/dist/commands/media.d.ts.map +1 -0
  7. package/dist/commands/media.js +124 -0
  8. package/dist/commands/media.js.map +1 -0
  9. package/dist/editor-pack/capabilities.d.ts.map +1 -1
  10. package/dist/editor-pack/capabilities.js +15 -0
  11. package/dist/editor-pack/capabilities.js.map +1 -1
  12. package/dist/generated/version.d.ts +1 -1
  13. package/dist/generated/version.js +1 -1
  14. package/dist/hosted/index.d.ts +1 -0
  15. package/dist/hosted/index.d.ts.map +1 -1
  16. package/dist/hosted/index.js +1 -0
  17. package/dist/hosted/index.js.map +1 -1
  18. package/dist/hosted/next/routes/mcp.d.ts.map +1 -1
  19. package/dist/hosted/next/routes/mcp.js +3 -0
  20. package/dist/hosted/next/routes/mcp.js.map +1 -1
  21. package/dist/mcp/prompts/onboarding.d.ts.map +1 -1
  22. package/dist/mcp/prompts/onboarding.js +11 -8
  23. package/dist/mcp/prompts/onboarding.js.map +1 -1
  24. package/dist/mcp/server.d.ts.map +1 -1
  25. package/dist/mcp/server.js +2 -0
  26. package/dist/mcp/server.js.map +1 -1
  27. package/dist/mcp/tools/media.d.ts +4 -0
  28. package/dist/mcp/tools/media.d.ts.map +1 -0
  29. package/dist/mcp/tools/media.js +138 -0
  30. package/dist/mcp/tools/media.js.map +1 -0
  31. package/dist/media-pipeline/actions.d.ts +30 -0
  32. package/dist/media-pipeline/actions.d.ts.map +1 -0
  33. package/dist/media-pipeline/actions.js +257 -0
  34. package/dist/media-pipeline/actions.js.map +1 -0
  35. package/dist/media-pipeline/client.d.ts +67 -0
  36. package/dist/media-pipeline/client.d.ts.map +1 -0
  37. package/dist/media-pipeline/client.js +75 -0
  38. package/dist/media-pipeline/client.js.map +1 -0
  39. package/dist/media-pipeline/config.d.ts +17 -0
  40. package/dist/media-pipeline/config.d.ts.map +1 -0
  41. package/dist/media-pipeline/config.js +52 -0
  42. package/dist/media-pipeline/config.js.map +1 -0
  43. package/dist/media-pipeline/import.d.ts +19 -0
  44. package/dist/media-pipeline/import.d.ts.map +1 -0
  45. package/dist/media-pipeline/import.js +198 -0
  46. package/dist/media-pipeline/import.js.map +1 -0
  47. package/dist/media-pipeline/index.d.ts +6 -0
  48. package/dist/media-pipeline/index.d.ts.map +1 -0
  49. package/dist/media-pipeline/index.js +6 -0
  50. package/dist/media-pipeline/index.js.map +1 -0
  51. package/dist/media-pipeline/job-store.d.ts +41 -0
  52. package/dist/media-pipeline/job-store.d.ts.map +1 -0
  53. package/dist/media-pipeline/job-store.js +172 -0
  54. package/dist/media-pipeline/job-store.js.map +1 -0
  55. package/dist/media-pipeline/limits.d.ts +14 -0
  56. package/dist/media-pipeline/limits.d.ts.map +1 -0
  57. package/dist/media-pipeline/limits.js +14 -0
  58. package/dist/media-pipeline/limits.js.map +1 -0
  59. package/dist/media-pipeline/log.d.ts +3 -0
  60. package/dist/media-pipeline/log.d.ts.map +1 -0
  61. package/dist/media-pipeline/log.js +12 -0
  62. package/dist/media-pipeline/log.js.map +1 -0
  63. package/dist/media-pipeline/validate.d.ts +18 -0
  64. package/dist/media-pipeline/validate.d.ts.map +1 -0
  65. package/dist/media-pipeline/validate.js +38 -0
  66. package/dist/media-pipeline/validate.js.map +1 -0
  67. package/dist/runtime/context.d.ts +5 -0
  68. package/dist/runtime/context.d.ts.map +1 -1
  69. package/dist/runtime/context.js.map +1 -1
  70. package/docs/editor-tasks/create-from-document.md +12 -3
  71. package/docs/editor-tasks/media-pipeline-prepare.md +124 -0
  72. package/docs/editor-tasks/media-reuse-and-upload.md +12 -3
  73. package/docs/editor-tasks/source-readiness-review.md +1 -0
  74. package/package.json +1 -1
@@ -0,0 +1,138 @@
1
+ import { z } from 'zod';
2
+ import { mediaCatalog, mediaImport, mediaPrepare, mediaStatus, mediaWait, } from '../../media-pipeline/actions.js';
3
+ import { MediaPipelineClient } from '../../media-pipeline/client.js';
4
+ import { loadMediaPipelineConfig } from '../../media-pipeline/config.js';
5
+ import { getRuntimeContext } from '../../runtime/context.js';
6
+ import { prefixHostedToolDescription } from '../hosted-metadata.js';
7
+ const TOOL_DESCRIPTION = `Prepare, inspect, and import media packs via the SE media pipeline (hosted cms-edit only).
8
+
9
+ Uses the host's service credentials — never ask the user for MEDIA_PIPELINE_API_KEY.
10
+
11
+ Actions:
12
+ - prepare: start a Drive folder pack job (returns jobId)
13
+ - status: poll job status / gap counts
14
+ - catalog: list ready assets (readyUrl, previewUrl) for naming/alt
15
+ - wait: poll until terminal or timeout (~25s default)
16
+ - import: reuse-first Contentful upload from readyUrl (optional dryRun, withMedia, overrides)
17
+
18
+ Workflow: prepare → wait → catalog → (draft titles/alts from previewUrl) → import.`;
19
+ function buildActionContext(hosted) {
20
+ const runtime = getRuntimeContext();
21
+ const config = loadMediaPipelineConfig();
22
+ return {
23
+ userId: runtime?.user?.userId ?? '',
24
+ projectKey: runtime?.projectKey ?? hosted?.projectKey ?? 'unknown',
25
+ config,
26
+ jobStore: runtime?.mediaPipelineJobStore ?? null,
27
+ client: config ? new MediaPipelineClient(config) : null,
28
+ };
29
+ }
30
+ export function registerMediaTools(server, hosted) {
31
+ server.tool('cms_edit_media', prefixHostedToolDescription(TOOL_DESCRIPTION, hosted), {
32
+ action: z
33
+ .enum(['prepare', 'status', 'catalog', 'wait', 'import'])
34
+ .describe('Media pipeline action'),
35
+ driveFolderUrl: z.string().optional().describe('Google Drive folder URL (prepare)'),
36
+ jobId: z.string().optional().describe('Job id from prepare (status/catalog/wait/import)'),
37
+ profile: z.string().optional().describe('Pipeline profile (default case-study-v1)'),
38
+ ttlHours: z.number().int().min(1).max(24).optional().describe('Pack TTL hours (1–24)'),
39
+ brief: z.string().optional().describe('Optional free-text brief stored on the job'),
40
+ waitTimeoutSec: z
41
+ .number()
42
+ .int()
43
+ .min(1)
44
+ .max(55)
45
+ .optional()
46
+ .describe('wait: max seconds to poll (default 25, max 55)'),
47
+ dryRun: z.boolean().optional().describe('import: preview map without uploading'),
48
+ withMedia: z.boolean().optional().describe('import: also create Media wrapper entries'),
49
+ overrides: z
50
+ .array(z.object({
51
+ sourceName: z.string(),
52
+ title: z.string().optional(),
53
+ alt: z.string().optional(),
54
+ fileName: z.string().optional(),
55
+ }))
56
+ .optional()
57
+ .describe('import: per-sourceName title/alt/fileName overrides'),
58
+ }, async (params) => {
59
+ const ctx = buildActionContext(hosted);
60
+ let result;
61
+ switch (params.action) {
62
+ case 'prepare':
63
+ if (!params.driveFolderUrl) {
64
+ result = {
65
+ ok: false,
66
+ error: 'driveFolderUrl is required for action=prepare',
67
+ code: 'invalid_input',
68
+ };
69
+ break;
70
+ }
71
+ result = await mediaPrepare(ctx, {
72
+ driveFolderUrl: params.driveFolderUrl,
73
+ profile: params.profile,
74
+ ttlHours: params.ttlHours,
75
+ brief: params.brief,
76
+ });
77
+ break;
78
+ case 'status':
79
+ if (!params.jobId) {
80
+ result = {
81
+ ok: false,
82
+ error: 'jobId is required for action=status',
83
+ code: 'invalid_input',
84
+ };
85
+ break;
86
+ }
87
+ result = await mediaStatus(ctx, { jobId: params.jobId });
88
+ break;
89
+ case 'catalog':
90
+ if (!params.jobId) {
91
+ result = {
92
+ ok: false,
93
+ error: 'jobId is required for action=catalog',
94
+ code: 'invalid_input',
95
+ };
96
+ break;
97
+ }
98
+ result = await mediaCatalog(ctx, { jobId: params.jobId });
99
+ break;
100
+ case 'wait':
101
+ if (!params.jobId) {
102
+ result = {
103
+ ok: false,
104
+ error: 'jobId is required for action=wait',
105
+ code: 'invalid_input',
106
+ };
107
+ break;
108
+ }
109
+ result = await mediaWait(ctx, {
110
+ jobId: params.jobId,
111
+ waitTimeoutSec: params.waitTimeoutSec,
112
+ });
113
+ break;
114
+ case 'import':
115
+ if (!params.jobId) {
116
+ result = {
117
+ ok: false,
118
+ error: 'jobId is required for action=import',
119
+ code: 'invalid_input',
120
+ };
121
+ break;
122
+ }
123
+ result = await mediaImport(ctx, {
124
+ jobId: params.jobId,
125
+ dryRun: params.dryRun,
126
+ withMedia: params.withMedia,
127
+ overrides: params.overrides,
128
+ });
129
+ break;
130
+ default:
131
+ result = { ok: false, error: `Unknown action: ${String(params.action)}` };
132
+ }
133
+ return {
134
+ content: [{ type: 'text', text: JSON.stringify(result) }],
135
+ };
136
+ });
137
+ }
138
+ //# sourceMappingURL=media.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media.js","sourceRoot":"","sources":["../../../src/mcp/tools/media.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import type { MediaPipelineClient } from './client.js';
2
+ import type { MediaPipelineConfig } from './config.js';
3
+ import type { MediaPipelineJobStore } from './job-store.js';
4
+ export interface MediaActionContext {
5
+ userId: string;
6
+ projectKey: string;
7
+ config: MediaPipelineConfig | null;
8
+ jobStore: MediaPipelineJobStore | null;
9
+ client: MediaPipelineClient | null;
10
+ }
11
+ export type MediaActionResult = Record<string, unknown>;
12
+ export declare function mediaPrepare(ctx: MediaActionContext, params: {
13
+ driveFolderUrl: string;
14
+ profile?: string;
15
+ ttlHours?: number;
16
+ brief?: string;
17
+ }): Promise<MediaActionResult>;
18
+ export declare function mediaStatus(ctx: MediaActionContext, params: {
19
+ jobId: string;
20
+ }): Promise<MediaActionResult>;
21
+ export declare function mediaCatalog(ctx: MediaActionContext, params: {
22
+ jobId: string;
23
+ }): Promise<MediaActionResult>;
24
+ export type { MediaImportOverride, MediaImportParams } from './import.js';
25
+ export { mediaImport } from './import.js';
26
+ export declare function mediaWait(ctx: MediaActionContext, params: {
27
+ jobId: string;
28
+ waitTimeoutSec?: number;
29
+ }): Promise<MediaActionResult>;
30
+ //# sourceMappingURL=actions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/media-pipeline/actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAA6B,MAAM,aAAa,CAAC;AAElF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD,OAAO,KAAK,EAAmB,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAU7E,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACnC,QAAQ,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACvC,MAAM,EAAE,mBAAmB,GAAG,IAAI,CAAC;CACpC;AAED,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAsCxD,wBAAsB,YAAY,CAChC,GAAG,EAAE,kBAAkB,EACvB,MAAM,EAAE;IACN,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GACA,OAAO,CAAC,iBAAiB,CAAC,CA0E5B;AAED,wBAAsB,WAAW,CAC/B,GAAG,EAAE,kBAAkB,EACvB,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GACxB,OAAO,CAAC,iBAAiB,CAAC,CA6B5B;AAED,wBAAsB,YAAY,CAChC,GAAG,EAAE,kBAAkB,EACvB,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GACxB,OAAO,CAAC,iBAAiB,CAAC,CAsD5B;AAED,YAAY,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAE1E,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,wBAAsB,SAAS,CAC7B,GAAG,EAAE,kBAAkB,EACvB,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,GACjD,OAAO,CAAC,iBAAiB,CAAC,CAmD5B"}
@@ -0,0 +1,257 @@
1
+ import { MediaPipelineClientError } from './client.js';
2
+ import { mediaPipelineConfiguredMessage } from './config.js';
3
+ import { MediaJobStoreError } from './job-store.js';
4
+ import { mediaPipelineLog } from './log.js';
5
+ import { assertAllowedProfile, clampTtlHours, isPlausibleJobId, parseDriveFolderUrl, } from './validate.js';
6
+ const TERMINAL = new Set(['succeeded', 'partial', 'failed']);
7
+ function ok(data) {
8
+ return { ok: true, ...data };
9
+ }
10
+ function fail(error, code, status, extra) {
11
+ return { ok: false, error, ...(code ? { code } : {}), ...(status ? { status } : {}), ...extra };
12
+ }
13
+ function requireHosted(ctx) {
14
+ if (!ctx.config || !ctx.client) {
15
+ return fail(mediaPipelineConfiguredMessage(), 'pipeline_unconfigured', 503);
16
+ }
17
+ if (!ctx.jobStore) {
18
+ return fail('Media pipeline job store requires hosted Redis (same as staged uploads).', 'store_unavailable', 503);
19
+ }
20
+ if (!ctx.userId) {
21
+ return fail('Media pipeline actions require an authenticated cms-edit user (Contentful OAuth).', 'unauthorized', 401);
22
+ }
23
+ return null;
24
+ }
25
+ export async function mediaPrepare(ctx, params) {
26
+ const blocked = requireHosted(ctx);
27
+ if (blocked) {
28
+ return blocked;
29
+ }
30
+ const config = ctx.config;
31
+ const jobStore = ctx.jobStore;
32
+ const client = ctx.client;
33
+ const folder = parseDriveFolderUrl(params.driveFolderUrl);
34
+ if (!folder.ok) {
35
+ return fail(folder.error, 'invalid_input', 400);
36
+ }
37
+ const profile = params.profile?.trim() || config.defaultProfile;
38
+ const profileOk = assertAllowedProfile(profile, config.allowedProfiles);
39
+ if (!profileOk.ok) {
40
+ return fail(profileOk.error, 'invalid_input', 400);
41
+ }
42
+ const ttlHours = clampTtlHours(params.ttlHours, config.defaultTtlHours);
43
+ try {
44
+ await jobStore.assertPrepareRateLimit(ctx.userId, config.preparePerHour);
45
+ await jobStore.assertConcurrentSlot(ctx.userId, config.maxConcurrentPerUser);
46
+ await jobStore.reserveConcurrentSlot(ctx.userId);
47
+ const prepared = await client.prepare({
48
+ profile,
49
+ driveFolderUrl: folder.url,
50
+ ttlHours,
51
+ brief: params.brief,
52
+ });
53
+ const binding = {
54
+ jobId: prepared.jobId,
55
+ userId: ctx.userId,
56
+ projectKey: ctx.projectKey,
57
+ tenant: config.tenant,
58
+ profile,
59
+ folderId: folder.folderId,
60
+ createdAt: Date.now(),
61
+ };
62
+ await jobStore.saveBinding(binding);
63
+ mediaPipelineLog('media.prepare', {
64
+ userId: ctx.userId,
65
+ projectKey: ctx.projectKey,
66
+ tenant: config.tenant,
67
+ jobId: prepared.jobId,
68
+ folderId: folder.folderId,
69
+ profile,
70
+ });
71
+ return ok({
72
+ jobId: prepared.jobId,
73
+ status: prepared.status,
74
+ tenant: config.tenant,
75
+ profile,
76
+ createdAt: prepared.createdAt,
77
+ expiresAt: prepared.expiresAt,
78
+ pollHint: 'Call cms_edit_media action=status or wait with this jobId until succeeded|partial|failed.',
79
+ });
80
+ }
81
+ catch (err) {
82
+ // Best-effort: free concurrent slot if we reserved but failed before binding
83
+ try {
84
+ await jobStore.releaseConcurrentSlot(ctx.userId);
85
+ }
86
+ catch {
87
+ // ignore
88
+ }
89
+ return mapActionError(err);
90
+ }
91
+ }
92
+ export async function mediaStatus(ctx, params) {
93
+ const blocked = requireHosted(ctx);
94
+ if (blocked) {
95
+ return blocked;
96
+ }
97
+ const jobId = params.jobId?.trim();
98
+ if (!jobId || !isPlausibleJobId(jobId)) {
99
+ return fail('jobId is required and must be a valid media job id.', 'invalid_input', 400);
100
+ }
101
+ try {
102
+ await ctx.jobStore.assertOwned(jobId, ctx.userId);
103
+ const status = await ctx.client.status(jobId);
104
+ await maybeReleaseConcurrent(ctx, jobId, status);
105
+ mediaPipelineLog('media.status', {
106
+ userId: ctx.userId,
107
+ projectKey: ctx.projectKey,
108
+ jobId,
109
+ status: status.status,
110
+ assetCount: status.assets?.length ?? 0,
111
+ gapCount: status.gaps?.length ?? 0,
112
+ });
113
+ return ok(summarizeStatus(status));
114
+ }
115
+ catch (err) {
116
+ return mapActionError(err);
117
+ }
118
+ }
119
+ export async function mediaCatalog(ctx, params) {
120
+ const blocked = requireHosted(ctx);
121
+ if (blocked) {
122
+ return blocked;
123
+ }
124
+ const jobId = params.jobId?.trim();
125
+ if (!jobId || !isPlausibleJobId(jobId)) {
126
+ return fail('jobId is required and must be a valid media job id.', 'invalid_input', 400);
127
+ }
128
+ try {
129
+ await ctx.jobStore.assertOwned(jobId, ctx.userId);
130
+ const status = await ctx.client.status(jobId);
131
+ await maybeReleaseConcurrent(ctx, jobId, status);
132
+ mediaPipelineLog('media.catalog', {
133
+ userId: ctx.userId,
134
+ projectKey: ctx.projectKey,
135
+ jobId,
136
+ status: status.status,
137
+ assetCount: status.assets?.length ?? 0,
138
+ });
139
+ const assets = (status.assets ?? []).map((a) => ({
140
+ id: a.id,
141
+ sourceName: a.sourceName,
142
+ fileName: a.fileName,
143
+ mime: a.mime,
144
+ bytes: a.bytes,
145
+ width: a.width,
146
+ height: a.height,
147
+ role: a.role,
148
+ readyUrl: a.url,
149
+ previewUrl: a.previewUrl ?? null,
150
+ posterUrl: a.posterUrl ?? null,
151
+ altDraft: a.altDraft ?? null,
152
+ titleDraft: a.titleDraft ?? null,
153
+ warnings: a.warnings ?? [],
154
+ }));
155
+ return ok({
156
+ jobId: status.jobId,
157
+ status: status.status,
158
+ profileId: status.profileId,
159
+ createdAt: status.createdAt,
160
+ expiresAt: status.expiresAt,
161
+ assets,
162
+ gaps: status.gaps ?? [],
163
+ note: 'Use previewUrl/posterUrl for naming and alt text. Use readyUrl only for asset upload --url (import).',
164
+ });
165
+ }
166
+ catch (err) {
167
+ return mapActionError(err);
168
+ }
169
+ }
170
+ // re-export import for single entry
171
+ export { mediaImport } from './import.js';
172
+ export async function mediaWait(ctx, params) {
173
+ const blocked = requireHosted(ctx);
174
+ if (blocked) {
175
+ return blocked;
176
+ }
177
+ const jobId = params.jobId?.trim();
178
+ if (!jobId || !isPlausibleJobId(jobId)) {
179
+ return fail('jobId is required and must be a valid media job id.', 'invalid_input', 400);
180
+ }
181
+ const timeoutSec = Math.min(Math.max(params.waitTimeoutSec ?? 25, 1), 55);
182
+ const deadline = Date.now() + timeoutSec * 1000;
183
+ try {
184
+ await ctx.jobStore.assertOwned(jobId, ctx.userId);
185
+ let last = null;
186
+ while (Date.now() < deadline) {
187
+ last = await ctx.client.status(jobId);
188
+ await maybeReleaseConcurrent(ctx, jobId, last);
189
+ if (TERMINAL.has(String(last.status))) {
190
+ mediaPipelineLog('media.wait', {
191
+ userId: ctx.userId,
192
+ projectKey: ctx.projectKey,
193
+ jobId,
194
+ status: last.status,
195
+ terminal: true,
196
+ });
197
+ return ok({ ...summarizeStatus(last), waited: true, terminal: true });
198
+ }
199
+ await sleep(2000);
200
+ }
201
+ mediaPipelineLog('media.wait', {
202
+ userId: ctx.userId,
203
+ projectKey: ctx.projectKey,
204
+ jobId,
205
+ status: last?.status ?? 'unknown',
206
+ terminal: false,
207
+ });
208
+ return ok({
209
+ ...(last ? summarizeStatus(last) : { jobId, status: 'unknown' }),
210
+ waited: true,
211
+ terminal: false,
212
+ note: 'Still running — call wait or status again.',
213
+ });
214
+ }
215
+ catch (err) {
216
+ return mapActionError(err);
217
+ }
218
+ }
219
+ function summarizeStatus(status) {
220
+ return {
221
+ jobId: status.jobId,
222
+ status: status.status,
223
+ tenant: status.tenant,
224
+ profileId: status.profileId,
225
+ createdAt: status.createdAt,
226
+ expiresAt: status.expiresAt,
227
+ assetCount: status.assets?.length ?? 0,
228
+ gapCount: status.gaps?.length ?? 0,
229
+ gaps: status.gaps ?? [],
230
+ error: status.error,
231
+ };
232
+ }
233
+ async function maybeReleaseConcurrent(ctx, jobId, status) {
234
+ if (!TERMINAL.has(String(status.status))) {
235
+ return;
236
+ }
237
+ try {
238
+ await ctx.jobStore.markConcurrentReleased(jobId, ctx.userId);
239
+ }
240
+ catch {
241
+ // ignore
242
+ }
243
+ }
244
+ function mapActionError(err) {
245
+ if (err instanceof MediaJobStoreError) {
246
+ return fail(err.message, err.code, err.status);
247
+ }
248
+ if (err instanceof MediaPipelineClientError) {
249
+ return fail(err.message, err.code, err.status);
250
+ }
251
+ const message = err instanceof Error ? err.message : String(err);
252
+ return fail(message, 'error', 500);
253
+ }
254
+ function sleep(ms) {
255
+ return new Promise((resolve) => setTimeout(resolve, ms));
256
+ }
257
+ //# sourceMappingURL=actions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.js","sourceRoot":"","sources":["../../src/media-pipeline/actions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ import type { MediaPipelineConfig } from './config.js';
2
+ export type PipelineJobStatus = 'queued' | 'running' | 'succeeded' | 'partial' | 'failed' | string;
3
+ export interface PipelineManifestAsset {
4
+ id: string;
5
+ sourceName: string;
6
+ fileName: string;
7
+ mime: string;
8
+ bytes: number;
9
+ width?: number;
10
+ height?: number;
11
+ role?: string;
12
+ url: string;
13
+ previewUrl?: string;
14
+ posterUrl?: string;
15
+ altDraft?: string;
16
+ titleDraft?: string;
17
+ warnings?: string[];
18
+ }
19
+ export interface PipelineManifestGap {
20
+ sourceName: string;
21
+ reason: string;
22
+ detail: string;
23
+ }
24
+ export interface PipelineJobStatusResponse {
25
+ jobId: string;
26
+ tenant: string;
27
+ status: PipelineJobStatus;
28
+ runId?: string;
29
+ createdAt: string;
30
+ expiresAt: string;
31
+ assets?: PipelineManifestAsset[];
32
+ gaps?: PipelineManifestGap[];
33
+ profileId?: string;
34
+ error?: string;
35
+ manifestUrl?: string;
36
+ reportUrl?: string;
37
+ statusUrl?: string;
38
+ }
39
+ export interface PipelinePrepareResponse {
40
+ jobId: string;
41
+ runId?: string;
42
+ status: PipelineJobStatus;
43
+ tenant: string;
44
+ createdAt: string;
45
+ expiresAt: string;
46
+ statusUrl?: string;
47
+ poll?: string;
48
+ error?: string;
49
+ }
50
+ export declare class MediaPipelineClientError extends Error {
51
+ readonly status: number;
52
+ readonly code?: string | undefined;
53
+ constructor(message: string, status: number, code?: string | undefined);
54
+ }
55
+ export declare class MediaPipelineClient {
56
+ private readonly config;
57
+ constructor(config: MediaPipelineConfig);
58
+ private headers;
59
+ prepare(params: {
60
+ profile: string;
61
+ driveFolderUrl: string;
62
+ ttlHours: number;
63
+ brief?: string;
64
+ }): Promise<PipelinePrepareResponse>;
65
+ status(jobId: string): Promise<PipelineJobStatusResponse>;
66
+ }
67
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/media-pipeline/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEnG,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,iBAAiB,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,qBAAqB,EAAE,CAAC;IACjC,IAAI,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,iBAAiB,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,wBAAyB,SAAQ,KAAK;IAG/C,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM;gBAFtB,OAAO,EAAE,MAAM,EACN,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,MAAM,YAAA;CAKzB;AAED,qBAAa,mBAAmB;IAClB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,mBAAmB;IAExD,OAAO,CAAC,OAAO;IAQT,OAAO,CAAC,MAAM,EAAE;QACpB,OAAO,EAAE,MAAM,CAAC;QAChB,cAAc,EAAE,MAAM,CAAC;QACvB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,GAAG,OAAO,CAAC,uBAAuB,CAAC;IA6B9B,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,CAAC;CAmBhE"}
@@ -0,0 +1,75 @@
1
+ export class MediaPipelineClientError extends Error {
2
+ status;
3
+ code;
4
+ constructor(message, status, code) {
5
+ super(message);
6
+ this.status = status;
7
+ this.code = code;
8
+ this.name = 'MediaPipelineClientError';
9
+ }
10
+ }
11
+ export class MediaPipelineClient {
12
+ config;
13
+ constructor(config) {
14
+ this.config = config;
15
+ }
16
+ headers() {
17
+ return {
18
+ Authorization: `Bearer ${this.config.serviceKey}`,
19
+ 'Content-Type': 'application/json',
20
+ Accept: 'application/json',
21
+ };
22
+ }
23
+ async prepare(params) {
24
+ const res = await fetch(`${this.config.baseUrl}/api/v1/jobs`, {
25
+ method: 'POST',
26
+ headers: this.headers(),
27
+ body: JSON.stringify({
28
+ tenant: this.config.tenant,
29
+ profile: params.profile,
30
+ source: {
31
+ type: 'google_drive_folder',
32
+ url: params.driveFolderUrl,
33
+ },
34
+ options: {
35
+ ttlHours: params.ttlHours,
36
+ ...(params.brief ? { brief: params.brief } : {}),
37
+ },
38
+ }),
39
+ });
40
+ const body = (await res.json().catch(() => ({})));
41
+ if (!res.ok) {
42
+ throw mapPipelineHttpError(res.status, body.error ?? res.statusText);
43
+ }
44
+ return body;
45
+ }
46
+ async status(jobId) {
47
+ const url = new URL(`${this.config.baseUrl}/api/v1/jobs/${encodeURIComponent(jobId)}`);
48
+ url.searchParams.set('tenant', this.config.tenant);
49
+ const res = await fetch(url.toString(), {
50
+ method: 'GET',
51
+ headers: this.headers(),
52
+ });
53
+ const body = (await res.json().catch(() => ({})));
54
+ if (!res.ok) {
55
+ throw mapPipelineHttpError(res.status, body.error ?? res.statusText);
56
+ }
57
+ return body;
58
+ }
59
+ }
60
+ function mapPipelineHttpError(status, detail) {
61
+ if (status === 401 || status === 403) {
62
+ return new MediaPipelineClientError('Media pipeline rejected the host service credentials (host misconfiguration). Contact ops — do not ask the user for a pipeline API key.', status, 'pipeline_auth');
63
+ }
64
+ if (status === 404) {
65
+ return new MediaPipelineClientError(detail || 'Media pipeline job not found.', status, 'not_found');
66
+ }
67
+ if (status === 429) {
68
+ return new MediaPipelineClientError(detail || 'Media pipeline rate limited the host.', status, 'rate_limited');
69
+ }
70
+ if (status >= 500) {
71
+ return new MediaPipelineClientError(detail || 'Media pipeline is unavailable.', status, 'pipeline_unavailable');
72
+ }
73
+ return new MediaPipelineClientError(detail || `Media pipeline error (${String(status)})`, status);
74
+ }
75
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/media-pipeline/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ export interface MediaPipelineConfig {
2
+ baseUrl: string;
3
+ serviceKey: string;
4
+ tenant: string;
5
+ defaultProfile: string;
6
+ allowedProfiles: string[];
7
+ preparePerHour: number;
8
+ maxConcurrentPerUser: number;
9
+ defaultTtlHours: number;
10
+ }
11
+ /**
12
+ * Load media-pipeline facade config from process env.
13
+ * Returns null when the host is not configured (do not ask agents for keys).
14
+ */
15
+ export declare function loadMediaPipelineConfig(envSource?: NodeJS.ProcessEnv): MediaPipelineConfig | null;
16
+ export declare function mediaPipelineConfiguredMessage(): string;
17
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/media-pipeline/config.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,eAAe,EAAE,MAAM,CAAC;CACzB;AAyBD;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,GAAE,MAAM,CAAC,UAAwB,GACzC,mBAAmB,GAAG,IAAI,CAgC5B;AAED,wBAAgB,8BAA8B,IAAI,MAAM,CAMvD"}