@clipwright/core 0.1.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,157 @@
1
+ import type { AspectRatio, Resolution } from "./skills.js";
2
+ /**
3
+ * Вендор-нейтральный контракт рендера аватара.
4
+ *
5
+ * ЗАЧЕМ: HeyGen — не единственный и не обязательно постоянный провайдер.
6
+ * В docs/05 уже записан Argil как запасной бэкенд (дешевле по минуте), а
7
+ * legacy-API самого HeyGen выключается 31.10.2026 — вендорская поверхность
8
+ * меняется быстрее, чем наш продукт. Всё, что знает про `x-api-key`,
9
+ * `/v3/videos` и словарь `"1080p"`, живёт ИСКЛЮЧИТЕЛЬНО в адаптере.
10
+ *
11
+ * ПРАВИЛО: ни один тип в этом файле не должен быть заимствован из вендорского
12
+ * ответа. Если поле нельзя объяснить в терминах продукта — ему здесь не место.
13
+ */
14
+ /** Источник аудио. Загруженный ассет — вендорская деталь, наружу не торчит. */
15
+ export type AudioSource = {
16
+ kind: "url";
17
+ url: string;
18
+ } | {
19
+ kind: "bytes";
20
+ data: Uint8Array;
21
+ mimeType: string;
22
+ };
23
+ /**
24
+ * Источник внешности актёра — ПУБЛИЧНЫЙ URL пользователя, и только он.
25
+ *
26
+ * Дискриминируемый союз из одного варианта не избыточен: он фиксирует, что
27
+ * вторым вариантом никогда не станет вендорский идентификатор. Расширяться он
28
+ * может кадром или байтами, но не «id аватара из каталога».
29
+ */
30
+ export type ImageSource = {
31
+ kind: "url";
32
+ url: string;
33
+ };
34
+ /**
35
+ * Запрос на рендер одного клипа.
36
+ *
37
+ * ПРО ВНЕШНОСТЬ (инвариант переписан в US-524, а не удалён). Раньше внешности
38
+ * здесь не было вовсе: её целиком разрешал адаптер, и поле `image` публичного
39
+ * контракта молча игнорировалось. Теперь внешность приходит СЮДА, но ровно в
40
+ * одной форме — пользовательский публичный URL. Вендорские идентификаторы
41
+ * (talking_photo, asset_id) наружу по-прежнему не торчат и остаются заперты в
42
+ * адаптере; `image` не задан — адаптер резолвит дефолтного актёра сам, как и
43
+ * прежде. То есть ослаблено ровно одно ограничение, и оно названо.
44
+ */
45
+ export interface AvatarClipRequest {
46
+ /**
47
+ * Наш run_id. Адаптер обязан пробросить его в идемпотентность вендора
48
+ * (`Idempotency-Key`, детерминированный от `runId` + шага), если она есть.
49
+ */
50
+ runId: string;
51
+ /**
52
+ * Область вендорской идемпотентности. Не задана — берётся `runId`, то есть
53
+ * поведение односегментного рана буквально прежнее.
54
+ *
55
+ * ЗАЧЕМ ОТДЕЛЬНО ОТ `runId` (US-526): у сегментного ролика на один ран
56
+ * приходится несколько платных create. Общий на всех ключ означал бы, что
57
+ * вендор дедуплицирует ВТОРОЙ сегмент в первый и вернёт чужой ролик —
58
+ * молча и с кодом 200. Разделять обязан вызывающий, потому что только он
59
+ * знает, чем сегменты различаются.
60
+ */
61
+ idempotencyScope?: string;
62
+ audio: AudioSource;
63
+ resolution: Resolution;
64
+ aspectRatio: AspectRatio;
65
+ /** Не задан — адаптер резолвит дефолтного актёра. */
66
+ image?: ImageSource;
67
+ }
68
+ export interface AvatarClipResult {
69
+ /**
70
+ * URL у вендора. Считается ВРЕМЕННЫМ и непригодным для отдачи клиенту:
71
+ * у HeyGen это подписанная ссылка с истечением. Пайплайн обязан перелить
72
+ * файл в наше хранилище и вернуть пользователю уже наш URL.
73
+ */
74
+ videoUrl: string;
75
+ durationSec: number;
76
+ /** Идентификатор задачи у вендора — без него разбор инцидента на их стороне невозможен. */
77
+ providerJobId: string;
78
+ provider: string;
79
+ }
80
+ /**
81
+ * Хэндл созданной у вендора задачи рендера — мост между ПЛАТНЫМ `createAvatarJob`
82
+ * и бесплатным `pollAvatarJob`.
83
+ *
84
+ * СЕРИАЛИЗУЕМ ПО КОНТРАКТУ (инвариант): таск сохраняет его в R2 как
85
+ * `runs/{runId}/avatar.job.json` и после падения восстанавливает через
86
+ * `JSON.parse(JSON.stringify(handle))`. Отсюда: только примитивы — никаких
87
+ * `Date`, `Uint8Array`, функций; если когда-либо понадобится момент времени,
88
+ * он кладётся ISO-строкой. Round-trip обязан давать эквивалентный объект,
89
+ * иначе возобновление поллинга по чекпоинту сломается.
90
+ */
91
+ export interface AvatarJobHandle {
92
+ /** Идентификатор задачи у вендора (у HeyGen — `video_id`). */
93
+ providerJobId: string;
94
+ /** Имя бэкенда, создавшего задачу, — чтобы поллить её тем же адаптером. */
95
+ provider: string;
96
+ }
97
+ /**
98
+ * Возможности бэкенда. Существует, потому что вендоры отличаются в мелочах,
99
+ * которые ломают гейт: HeyGen отклоняет `480p` и ниже (проверено 18.07.2026,
100
+ * принимает только 720p/1080p/4k), у другого провайдера набор будет иным.
101
+ *
102
+ * Несовпадение НЕ должно приводить к молчаливой подмене — правило проекта №3
103
+ * требует объявлять несоблюдённые параметры в `warnings[]`.
104
+ */
105
+ export interface BackendCapabilities {
106
+ resolutions: readonly Resolution[];
107
+ aspectRatios: readonly AspectRatio[];
108
+ /** Максимальная длина одного аудио, сек. null — вендор не документирует. */
109
+ maxAudioSec: number | null;
110
+ }
111
+ export interface RenderBackend {
112
+ readonly name: string;
113
+ readonly capabilities: BackendCapabilities;
114
+ /**
115
+ * Создать задачу рендера у вендора. РАЗДЕЛЁН с поллингом намеренно: создание —
116
+ * ПЛАТНОЕ и однократное, поллинг — бесплатный и возобновляемый. Возврат —
117
+ * сериализуемый `AvatarJobHandle`, который таск пишет в R2 (`avatar.job.json`)
118
+ * ДО начала поллинга: падение между «вендор списал деньги» и «ролик перелит в
119
+ * наше хранилище» возобновляется поллингом сохранённого handle, а не вторым
120
+ * платным вызовом (ADR-006).
121
+ *
122
+ * Реализация обязана:
123
+ * - разрешать внешность персонажа ВНУТРИ (вендорский каталог), не принимая её
124
+ * из запроса — идентификаторы аватара и каталог наружу не торчат;
125
+ * - слать вендорский `Idempotency-Key`, детерминированный от `req.runId`, —
126
+ * вторая независимая линия против двойного списания (подтверждено, docs/13);
127
+ * - бросать НЕрайтраебл-ошибку на 4xx кроме 429 — ретрай их не починит;
128
+ * - бросать райтраебл-ошибку на 5xx/429 — их ретрай починит.
129
+ */
130
+ createAvatarJob(req: AvatarClipRequest): Promise<AvatarJobHandle>;
131
+ /**
132
+ * Дождаться терминального состояния задачи и вернуть результат. БЕСПЛАТНЫЙ и
133
+ * идемпотентный: безопасен для повтора и для возобновления по сохранённому
134
+ * handle.
135
+ *
136
+ * Дискриминация терминальности — SUCCESS-WHITELIST (fail-closed): успех — это
137
+ * ТОЛЬКО явно успешный статус вендора ВМЕСТЕ с непустым URL видео. Любой иной
138
+ * терминальный ИЛИ неизвестный статус, равно как «успех» без URL, обязан
139
+ * бросать: молчаливый провал внутри HTTP 200 (у HeyGen — `status: "failed"`)
140
+ * обычным error-handling не ловится. Реализация обязана иметь верхний предел
141
+ * ожидания и бросать по его превышению, а не висеть на зависшей очереди.
142
+ */
143
+ pollAvatarJob(handle: AvatarJobHandle): Promise<AvatarClipResult>;
144
+ }
145
+ /**
146
+ * Приведение запроса к возможностям бэкенда.
147
+ *
148
+ * Возвращает то, что бэкенд реально может, и список предупреждений о каждом
149
+ * расхождении. Молча понижать разрешение нельзя: пользователь, заказавший
150
+ * 1080p и получивший 720p без предупреждения, — это ровно тот дефект
151
+ * конкурента, который зафиксирован в docs/08 как антипаттерн.
152
+ */
153
+ export declare function negotiateCapabilities(req: Pick<AvatarClipRequest, "resolution" | "aspectRatio">, caps: BackendCapabilities, backendName: string): {
154
+ resolution: Resolution;
155
+ aspectRatio: AspectRatio;
156
+ warnings: string[];
157
+ };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Приведение запроса к возможностям бэкенда.
3
+ *
4
+ * Возвращает то, что бэкенд реально может, и список предупреждений о каждом
5
+ * расхождении. Молча понижать разрешение нельзя: пользователь, заказавший
6
+ * 1080p и получивший 720p без предупреждения, — это ровно тот дефект
7
+ * конкурента, который зафиксирован в docs/08 как антипаттерн.
8
+ */
9
+ export function negotiateCapabilities(req, caps, backendName) {
10
+ const warnings = [];
11
+ let resolution = req.resolution;
12
+ if (!caps.resolutions.includes(resolution)) {
13
+ const fallback = caps.resolutions.at(-1);
14
+ if (!fallback) {
15
+ throw new Error(`backend ${backendName} declares no supported resolutions`);
16
+ }
17
+ warnings.push(`resolution ${resolution} is not supported by ${backendName}; rendered at ${fallback}`);
18
+ resolution = fallback;
19
+ }
20
+ let aspectRatio = req.aspectRatio;
21
+ if (!caps.aspectRatios.includes(aspectRatio)) {
22
+ const fallback = caps.aspectRatios[0];
23
+ if (!fallback) {
24
+ throw new Error(`backend ${backendName} declares no supported aspect ratios`);
25
+ }
26
+ warnings.push(`aspect_ratio ${aspectRatio} is not supported by ${backendName}; rendered at ${fallback}`);
27
+ aspectRatio = fallback;
28
+ }
29
+ return { resolution, aspectRatio, warnings };
30
+ }
31
+ //# sourceMappingURL=render-backend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-backend.js","sourceRoot":"","sources":["../src/render-backend.ts"],"names":[],"mappings":"AAmJA;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CACnC,GAA0D,EAC1D,IAAyB,EACzB,WAAmB;IAEnB,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,IAAI,UAAU,GAAG,GAAG,CAAC,UAAU,CAAC;IAChC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACzC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,WAAW,WAAW,oCAAoC,CAAC,CAAC;QAC9E,CAAC;QACD,QAAQ,CAAC,IAAI,CACX,cAAc,UAAU,wBAAwB,WAAW,iBAAiB,QAAQ,EAAE,CACvF,CAAC;QACF,UAAU,GAAG,QAAQ,CAAC;IACxB,CAAC;IAED,IAAI,WAAW,GAAG,GAAG,CAAC,WAAW,CAAC;IAClC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QAC7C,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACtC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,WAAW,WAAW,sCAAsC,CAAC,CAAC;QAChF,CAAC;QACD,QAAQ,CAAC,IAAI,CACX,gBAAgB,WAAW,wBAAwB,WAAW,iBAAiB,QAAQ,EAAE,CAC1F,CAAC;QACF,WAAW,GAAG,QAAQ,CAAC;IACzB,CAAC;IAED,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC;AAC/C,CAAC"}
package/dist/runs.d.ts ADDED
@@ -0,0 +1,176 @@
1
+ import { z } from "zod";
2
+ /** Стадии рендер-пайплайна make_ugc (docs/02 §2). */
3
+ export declare const RUN_STATES: readonly ["queued", "scripting", "tts", "avatar", "compositing", "uploading", "succeeded", "failed"];
4
+ export declare const runState: z.ZodEnum<{
5
+ avatar: "avatar";
6
+ compositing: "compositing";
7
+ failed: "failed";
8
+ queued: "queued";
9
+ scripting: "scripting";
10
+ succeeded: "succeeded";
11
+ tts: "tts";
12
+ uploading: "uploading";
13
+ }>;
14
+ export type RunState = z.infer<typeof runState>;
15
+ export declare const runStep: z.ZodObject<{
16
+ step: z.ZodString;
17
+ state: z.ZodEnum<{
18
+ failed: "failed";
19
+ pending: "pending";
20
+ running: "running";
21
+ succeeded: "succeeded";
22
+ }>;
23
+ started_at: z.ZodNullable<z.ZodString>;
24
+ finished_at: z.ZodNullable<z.ZodString>;
25
+ artifacts: z.ZodDefault<z.ZodArray<z.ZodObject<{
26
+ kind: z.ZodString;
27
+ url: z.ZodString;
28
+ bytes: z.ZodNumber;
29
+ }, z.core.$strip>>>;
30
+ }, z.core.$strip>;
31
+ /**
32
+ * Единый объект рана для ЛЮБОГО скилла и маршрута — один неймспейс
33
+ * /v1/runs/{id}, без протечки внутреннего роутинга (урок из teardown
34
+ * AgentMedia: реюз-раны у них 404-ят на документированном эндпоинте).
35
+ */
36
+ /**
37
+ * МАРКИРОВКА AI-ВЫВОДА — константа, а не строка на каждой поверхности.
38
+ *
39
+ * Ст. 50 EU AI Act применяется с 2026-08-02. Юридическую применимость к
40
+ * прототипу мы не проверяли и не выдаём техническую меру за соответствие; но
41
+ * ролик, синтезированный целиком, обязан говорить об этом сам — независимо от
42
+ * того, кто его потом перешлёт.
43
+ *
44
+ * Текст живёт ОДНОЙ константой: три поверхности (SDK, CLI, MCP) отдают его
45
+ * дословно, и разъехавшиеся формулировки означали бы, что раскрытие зависит от
46
+ * того, каким клиентом пользователь скачал файл.
47
+ */
48
+ export declare const AI_DISCLOSURE_TEXT = "This video was generated with AI: the actor, the voice and the lip sync are synthetic.";
49
+ /**
50
+ * Метаданные объекта хранилища (вариант D2) — одна константа, потому что
51
+ * ключи должны совпадать у писателя и у любого будущего читателя. Носитель
52
+ * слабее встроенного тега (теряется при пересохранении), зато проставляется
53
+ * всегда и детерминированно, без разбора боксов MP4.
54
+ */
55
+ export declare const AI_DISCLOSURE_OBJECT_METADATA: Readonly<Record<string, string>>;
56
+ export declare const run: z.ZodObject<{
57
+ run_id: z.ZodString;
58
+ skill: z.ZodString;
59
+ state: z.ZodEnum<{
60
+ avatar: "avatar";
61
+ compositing: "compositing";
62
+ failed: "failed";
63
+ queued: "queued";
64
+ scripting: "scripting";
65
+ succeeded: "succeeded";
66
+ tts: "tts";
67
+ uploading: "uploading";
68
+ }>;
69
+ credits_reserved: z.ZodNumber;
70
+ credits_charged: z.ZodNullable<z.ZodNumber>;
71
+ warnings: z.ZodDefault<z.ZodArray<z.ZodString>>;
72
+ error: z.ZodNullable<z.ZodString>;
73
+ final_output: z.ZodNullable<z.ZodObject<{
74
+ video_url: z.ZodString;
75
+ video_url_unsubtitled: z.ZodOptional<z.ZodString>;
76
+ portrait_url: z.ZodOptional<z.ZodString>;
77
+ character_sheet_url: z.ZodOptional<z.ZodString>;
78
+ character_id: z.ZodOptional<z.ZodString>;
79
+ duration_seconds: z.ZodNumber;
80
+ ai_generated: z.ZodLiteral<true>;
81
+ ai_disclosure: z.ZodString;
82
+ requested_aspect_ratio: z.ZodOptional<z.ZodString>;
83
+ vendor_aspect_ratio: z.ZodOptional<z.ZodString>;
84
+ final_aspect_ratio: z.ZodOptional<z.ZodString>;
85
+ composition_policy: z.ZodOptional<z.ZodString>;
86
+ }, z.core.$strip>>;
87
+ steps: z.ZodDefault<z.ZodArray<z.ZodObject<{
88
+ step: z.ZodString;
89
+ state: z.ZodEnum<{
90
+ failed: "failed";
91
+ pending: "pending";
92
+ running: "running";
93
+ succeeded: "succeeded";
94
+ }>;
95
+ started_at: z.ZodNullable<z.ZodString>;
96
+ finished_at: z.ZodNullable<z.ZodString>;
97
+ artifacts: z.ZodDefault<z.ZodArray<z.ZodObject<{
98
+ kind: z.ZodString;
99
+ url: z.ZodString;
100
+ bytes: z.ZodNumber;
101
+ }, z.core.$strip>>>;
102
+ }, z.core.$strip>>>;
103
+ created_at: z.ZodString;
104
+ finished_at: z.ZodNullable<z.ZodString>;
105
+ }, z.core.$strip>;
106
+ export type Run = z.infer<typeof run>;
107
+ /**
108
+ * Терминальные состояния рана — РАЗДЕЛЯЕМАЯ константа, единственный источник
109
+ * истины о завершённости. ЗАЧЕМ отдельно, а не строковые литералы: до US-501
110
+ * терминальность проверялась `"succeeded"`/`"failed"` в трёх местах (SDK-цикл
111
+ * `makeUgc`, две ветки MCP `get_run`); расхождение при правке ловилось бы только
112
+ * в проде.
113
+ */
114
+ export declare const SUCCEEDED_STATE: "succeeded";
115
+ export declare const FAILED_STATE: "failed";
116
+ export declare const TERMINAL_STATES: readonly ["succeeded", "failed"];
117
+ /**
118
+ * Толерантная READ-проекция рана: ослабляется РОВНО одно поле — `state`
119
+ * (enum `runState` → `z.string()`). Все остальные поля, включая
120
+ * `final_output.portrait_url`/`character_*`, НАСЛЕДУЮТСЯ через `run.extend` —
121
+ * рукописный форк схемы ЗАПРЕЩЁН (связывающее условие Architect A11): форк молча
122
+ * ронял бы новые поля `run` из read-контракта, что и есть тот класс дефектов.
123
+ *
124
+ * ЗАЧЕМ read/write-асимметрия (docs/12:198): добавление стадии на сервере
125
+ * (напр. `"publishing"`) сломало бы строгий `run.parse` в `getRun` у КАЖДОГО уже
126
+ * установленного MCP — неизвестная стадия отверглась бы. Чтение толерантно к
127
+ * расширению enum стадий; строгость ЗАПИСИ (`run.parse` в `startUgc`) при этом
128
+ * сохраняется — сервер не должен принимать мусорное тело.
129
+ */
130
+ export declare const runRead: z.ZodObject<{
131
+ run_id: z.ZodString;
132
+ skill: z.ZodString;
133
+ credits_reserved: z.ZodNumber;
134
+ credits_charged: z.ZodNullable<z.ZodNumber>;
135
+ warnings: z.ZodDefault<z.ZodArray<z.ZodString>>;
136
+ error: z.ZodNullable<z.ZodString>;
137
+ final_output: z.ZodNullable<z.ZodObject<{
138
+ video_url: z.ZodString;
139
+ video_url_unsubtitled: z.ZodOptional<z.ZodString>;
140
+ portrait_url: z.ZodOptional<z.ZodString>;
141
+ character_sheet_url: z.ZodOptional<z.ZodString>;
142
+ character_id: z.ZodOptional<z.ZodString>;
143
+ duration_seconds: z.ZodNumber;
144
+ ai_generated: z.ZodLiteral<true>;
145
+ ai_disclosure: z.ZodString;
146
+ requested_aspect_ratio: z.ZodOptional<z.ZodString>;
147
+ vendor_aspect_ratio: z.ZodOptional<z.ZodString>;
148
+ final_aspect_ratio: z.ZodOptional<z.ZodString>;
149
+ composition_policy: z.ZodOptional<z.ZodString>;
150
+ }, z.core.$strip>>;
151
+ steps: z.ZodDefault<z.ZodArray<z.ZodObject<{
152
+ step: z.ZodString;
153
+ state: z.ZodEnum<{
154
+ failed: "failed";
155
+ pending: "pending";
156
+ running: "running";
157
+ succeeded: "succeeded";
158
+ }>;
159
+ started_at: z.ZodNullable<z.ZodString>;
160
+ finished_at: z.ZodNullable<z.ZodString>;
161
+ artifacts: z.ZodDefault<z.ZodArray<z.ZodObject<{
162
+ kind: z.ZodString;
163
+ url: z.ZodString;
164
+ bytes: z.ZodNumber;
165
+ }, z.core.$strip>>>;
166
+ }, z.core.$strip>>>;
167
+ created_at: z.ZodString;
168
+ finished_at: z.ZodNullable<z.ZodString>;
169
+ state: z.ZodString;
170
+ }, z.core.$strip>;
171
+ export type RunRead = z.infer<typeof runRead>;
172
+ /**
173
+ * Терминален ли ран. Принимает `string` (read-проекция), а не `RunState`,
174
+ * поэтому неизвестный статус (расширение enum) корректно даёт `false`.
175
+ */
176
+ export declare function isTerminal(state: string): boolean;
package/dist/runs.js ADDED
@@ -0,0 +1,137 @@
1
+ import { z } from "zod";
2
+ /** Стадии рендер-пайплайна make_ugc (docs/02 §2). */
3
+ export const RUN_STATES = [
4
+ "queued",
5
+ "scripting",
6
+ "tts",
7
+ "avatar",
8
+ "compositing",
9
+ "uploading",
10
+ "succeeded",
11
+ "failed",
12
+ ];
13
+ export const runState = z.enum(RUN_STATES);
14
+ export const runStep = z.object({
15
+ step: z.string(),
16
+ state: z.enum(["pending", "running", "succeeded", "failed"]),
17
+ started_at: z.string().datetime().nullable(),
18
+ finished_at: z.string().datetime().nullable(),
19
+ artifacts: z
20
+ .array(z.object({ kind: z.string(), url: z.string().url(), bytes: z.number().int() }))
21
+ .default([]),
22
+ });
23
+ /**
24
+ * Единый объект рана для ЛЮБОГО скилла и маршрута — один неймспейс
25
+ * /v1/runs/{id}, без протечки внутреннего роутинга (урок из teardown
26
+ * AgentMedia: реюз-раны у них 404-ят на документированном эндпоинте).
27
+ */
28
+ /**
29
+ * МАРКИРОВКА AI-ВЫВОДА — константа, а не строка на каждой поверхности.
30
+ *
31
+ * Ст. 50 EU AI Act применяется с 2026-08-02. Юридическую применимость к
32
+ * прототипу мы не проверяли и не выдаём техническую меру за соответствие; но
33
+ * ролик, синтезированный целиком, обязан говорить об этом сам — независимо от
34
+ * того, кто его потом перешлёт.
35
+ *
36
+ * Текст живёт ОДНОЙ константой: три поверхности (SDK, CLI, MCP) отдают его
37
+ * дословно, и разъехавшиеся формулировки означали бы, что раскрытие зависит от
38
+ * того, каким клиентом пользователь скачал файл.
39
+ */
40
+ export const AI_DISCLOSURE_TEXT = "This video was generated with AI: the actor, the voice and the lip sync are synthetic.";
41
+ /**
42
+ * Метаданные объекта хранилища (вариант D2) — одна константа, потому что
43
+ * ключи должны совпадать у писателя и у любого будущего читателя. Носитель
44
+ * слабее встроенного тега (теряется при пересохранении), зато проставляется
45
+ * всегда и детерминированно, без разбора боксов MP4.
46
+ */
47
+ export const AI_DISCLOSURE_OBJECT_METADATA = {
48
+ "ai-generated": "true",
49
+ generator: "clipwright",
50
+ "ai-disclosure": AI_DISCLOSURE_TEXT,
51
+ };
52
+ export const run = z.object({
53
+ run_id: z.string().regex(/^run_[a-zA-Z0-9]+$/),
54
+ skill: z.string(),
55
+ state: runState,
56
+ credits_reserved: z.number().int().nonnegative(),
57
+ credits_charged: z.number().int().nonnegative().nullable(),
58
+ warnings: z.array(z.string()).default([]),
59
+ error: z.string().nullable(),
60
+ final_output: z
61
+ .object({
62
+ video_url: z.string().url(),
63
+ video_url_unsubtitled: z.string().url().optional(),
64
+ portrait_url: z.string().url().optional(),
65
+ character_sheet_url: z.string().url().optional(),
66
+ character_id: z.string().optional(),
67
+ duration_seconds: z.number().positive(),
68
+ /**
69
+ * ОБЯЗАТЕЛЬНЫЕ поля маркировки, а не опциональные.
70
+ *
71
+ * `z.literal(true)` — не педантизм: опциональное поле можно забыть
72
+ * проставить, и ролик уедет непомеченным, а схема промолчит. Здесь она
73
+ * не промолчит — `run.parse` уронит терминальный ответ, и дефект
74
+ * обнаружится у нас, а не у получателя ролика.
75
+ */
76
+ ai_generated: z.literal(true),
77
+ ai_disclosure: z.string().min(1),
78
+ /**
79
+ * ТРИ РАЗНЫХ ФОРМАТА, А НЕ ОДИН (US-527).
80
+ *
81
+ * До этой стори ответ нёс одно значение, и по нему нельзя было отличить
82
+ * «клиент попросил 1:1 и получил 1:1» от «клиент попросил 1:1, вендору
83
+ * ушло 9:16, а квадрат собрали мы». Живое измерение US-530 показало, что
84
+ * разница видимая: вендор в одну сторону режет кадр, в другую добивает
85
+ * белым. Отладка расхождения по одному полю невозможна — оно не хранит
86
+ * того, что произошло.
87
+ *
88
+ * Поля опциональны, потому что заполняются только на пути композиции:
89
+ * ран без неё отдаёт вендорский файл как есть, и трёх значений у него
90
+ * нет — есть одно.
91
+ */
92
+ requested_aspect_ratio: z.string().optional(),
93
+ vendor_aspect_ratio: z.string().optional(),
94
+ final_aspect_ratio: z.string().optional(),
95
+ /** Как клип уложен в кадр: `exact`, либо режим фона. */
96
+ composition_policy: z.string().optional(),
97
+ })
98
+ .nullable(),
99
+ steps: z.array(runStep).default([]),
100
+ created_at: z.string().datetime(),
101
+ finished_at: z.string().datetime().nullable(),
102
+ });
103
+ /**
104
+ * Терминальные состояния рана — РАЗДЕЛЯЕМАЯ константа, единственный источник
105
+ * истины о завершённости. ЗАЧЕМ отдельно, а не строковые литералы: до US-501
106
+ * терминальность проверялась `"succeeded"`/`"failed"` в трёх местах (SDK-цикл
107
+ * `makeUgc`, две ветки MCP `get_run`); расхождение при правке ловилось бы только
108
+ * в проде.
109
+ */
110
+ export const SUCCEEDED_STATE = "succeeded";
111
+ export const FAILED_STATE = "failed";
112
+ // Различение исхода у потребителей — ТОЛЬКО через именованные константы выше:
113
+ // позиционный `TERMINAL_STATES[1]` при переупорядочивании массива молча
114
+ // инвертировал бы успех/провал (вердикт Architect, Фаза 2 Этапа 5).
115
+ export const TERMINAL_STATES = [SUCCEEDED_STATE, FAILED_STATE];
116
+ /**
117
+ * Толерантная READ-проекция рана: ослабляется РОВНО одно поле — `state`
118
+ * (enum `runState` → `z.string()`). Все остальные поля, включая
119
+ * `final_output.portrait_url`/`character_*`, НАСЛЕДУЮТСЯ через `run.extend` —
120
+ * рукописный форк схемы ЗАПРЕЩЁН (связывающее условие Architect A11): форк молча
121
+ * ронял бы новые поля `run` из read-контракта, что и есть тот класс дефектов.
122
+ *
123
+ * ЗАЧЕМ read/write-асимметрия (docs/12:198): добавление стадии на сервере
124
+ * (напр. `"publishing"`) сломало бы строгий `run.parse` в `getRun` у КАЖДОГО уже
125
+ * установленного MCP — неизвестная стадия отверглась бы. Чтение толерантно к
126
+ * расширению enum стадий; строгость ЗАПИСИ (`run.parse` в `startUgc`) при этом
127
+ * сохраняется — сервер не должен принимать мусорное тело.
128
+ */
129
+ export const runRead = run.extend({ state: z.string() });
130
+ /**
131
+ * Терминален ли ран. Принимает `string` (read-проекция), а не `RunState`,
132
+ * поэтому неизвестный статус (расширение enum) корректно даёт `false`.
133
+ */
134
+ export function isTerminal(state) {
135
+ return TERMINAL_STATES.includes(state);
136
+ }
137
+ //# sourceMappingURL=runs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runs.js","sourceRoot":"","sources":["../src/runs.ts"],"names":[],"mappings":"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"}