@alchemint/fuse-cli 0.0.0-stage → 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.
- package/README.md +48 -2
- package/package.json +20 -4
- package/src/assets.mjs +386 -0
- package/src/auth.mjs +132 -0
- package/src/batch.mjs +523 -0
- package/src/core.mjs +353 -0
- package/src/creative.mjs +379 -0
- package/src/download.mjs +121 -0
- package/src/element-images.mjs +257 -0
- package/src/main.mjs +276 -0
- package/src/mutations.mjs +125 -0
- package/src/prompt.mjs +121 -0
- package/src/versions.mjs +358 -0
- package/src/video.mjs +349 -0
package/src/prompt.mjs
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import {
|
|
3
|
+
CliError,
|
|
4
|
+
need,
|
|
5
|
+
id,
|
|
6
|
+
readJson,
|
|
7
|
+
writeJson,
|
|
8
|
+
digest,
|
|
9
|
+
subject,
|
|
10
|
+
errorData,
|
|
11
|
+
} from './core.mjs';
|
|
12
|
+
import { recordedMutation } from './mutations.mjs';
|
|
13
|
+
import { version as versionCommand } from './versions.mjs';
|
|
14
|
+
|
|
15
|
+
const refinementSummary = (row) => ({
|
|
16
|
+
request_id: row.request_id,
|
|
17
|
+
status: row.status,
|
|
18
|
+
model: row.model,
|
|
19
|
+
refined_sha256:
|
|
20
|
+
typeof row.refined_prompt === 'string' ? digest(row.refined_prompt) : null,
|
|
21
|
+
refined_characters:
|
|
22
|
+
typeof row.refined_prompt === 'string' ? row.refined_prompt.length : 0,
|
|
23
|
+
error: row.error,
|
|
24
|
+
});
|
|
25
|
+
async function present(value, summary, options) {
|
|
26
|
+
if (options.out) {
|
|
27
|
+
await writeJson(options.out, value, true);
|
|
28
|
+
return { ...summary, file: path.resolve(options.out) };
|
|
29
|
+
}
|
|
30
|
+
return options.full ? value : summary;
|
|
31
|
+
}
|
|
32
|
+
export async function prompt(action, options, context) {
|
|
33
|
+
if (action === 'optimize') {
|
|
34
|
+
const body = options.resume
|
|
35
|
+
? undefined
|
|
36
|
+
: await readJson(
|
|
37
|
+
need(
|
|
38
|
+
options['request-file'],
|
|
39
|
+
'--request-file (prompt refinement body)',
|
|
40
|
+
),
|
|
41
|
+
);
|
|
42
|
+
return recordedMutation(
|
|
43
|
+
'prompt_optimize',
|
|
44
|
+
'/prompt-refinements',
|
|
45
|
+
body,
|
|
46
|
+
options,
|
|
47
|
+
context,
|
|
48
|
+
(row) => ({ request_id: row.request_id, status: row.status }),
|
|
49
|
+
true,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
if (action === 'result') {
|
|
53
|
+
const row = await context.client.get(
|
|
54
|
+
`/prompt-refinements/${id(options.job, '--job (refinement request id)')}`,
|
|
55
|
+
);
|
|
56
|
+
return present(row, refinementSummary(row), options);
|
|
57
|
+
}
|
|
58
|
+
if (action === 'adopt') {
|
|
59
|
+
// Adoption writes the explicitly supplied draft onto one Shot version; it is not an
|
|
60
|
+
// automatic overwrite with the suggestion. Both texts stay in a private local record.
|
|
61
|
+
const body = await readJson(
|
|
62
|
+
need(
|
|
63
|
+
options['request-file'],
|
|
64
|
+
'--request-file (version draft input with the exact adopted prompt_text)',
|
|
65
|
+
),
|
|
66
|
+
);
|
|
67
|
+
const suggestion = await context.client.get(
|
|
68
|
+
`/prompt-refinements/${id(options.job, '--job (suggestion provenance)')}`,
|
|
69
|
+
);
|
|
70
|
+
if (suggestion.status !== 'succeeded' || suggestion.refined_prompt === null)
|
|
71
|
+
throw new CliError(
|
|
72
|
+
4,
|
|
73
|
+
'SUGGESTION_NOT_READY',
|
|
74
|
+
'The specified suggestion is not ready for adoption.',
|
|
75
|
+
);
|
|
76
|
+
let adoption;
|
|
77
|
+
if (options.receipt) {
|
|
78
|
+
adoption = {
|
|
79
|
+
schema_version: 1,
|
|
80
|
+
kind: 'prompt_adoption',
|
|
81
|
+
...(await subject(context.client)),
|
|
82
|
+
shot_id: Number(need(options.shot, '--shot')),
|
|
83
|
+
version_number: Number(need(options.number, '--number')),
|
|
84
|
+
stage: options.stage ?? 'i2v',
|
|
85
|
+
source_request_id: options.job,
|
|
86
|
+
suggestion_text: suggestion.refined_prompt,
|
|
87
|
+
adopted_text: body.prompt_text,
|
|
88
|
+
request: body,
|
|
89
|
+
state: 'prepared',
|
|
90
|
+
};
|
|
91
|
+
await writeJson(options.receipt, adoption, true);
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const saved = await versionCommand(
|
|
95
|
+
'draft',
|
|
96
|
+
'set',
|
|
97
|
+
{ ...options, 'request-file': options['request-file'] },
|
|
98
|
+
context,
|
|
99
|
+
);
|
|
100
|
+
if (adoption) {
|
|
101
|
+
adoption.state = 'saved';
|
|
102
|
+
adoption.readback_sha256 = saved.input_sha256 ?? digest(saved.input);
|
|
103
|
+
await writeJson(options.receipt, adoption);
|
|
104
|
+
}
|
|
105
|
+
return saved;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
if (adoption) {
|
|
108
|
+
adoption.state = 'unconfirmed';
|
|
109
|
+
adoption.error = errorData(error);
|
|
110
|
+
await writeJson(options.receipt, adoption);
|
|
111
|
+
error.receipt = options.receipt;
|
|
112
|
+
}
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
throw new CliError(
|
|
117
|
+
2,
|
|
118
|
+
'UNKNOWN_COMMAND',
|
|
119
|
+
'Use prompt optimize, result, or adopt (drafts live on Shot versions: fuse version …).',
|
|
120
|
+
);
|
|
121
|
+
}
|
package/src/versions.mjs
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import {
|
|
4
|
+
CliError,
|
|
5
|
+
need,
|
|
6
|
+
id,
|
|
7
|
+
digest,
|
|
8
|
+
readJson,
|
|
9
|
+
writeJson,
|
|
10
|
+
exists,
|
|
11
|
+
subject,
|
|
12
|
+
sameSubject,
|
|
13
|
+
withWriter,
|
|
14
|
+
} from './core.mjs';
|
|
15
|
+
import { acceptVideo, observeVideo, validateIntent } from './video.mjs';
|
|
16
|
+
|
|
17
|
+
// A Shot version is numbered when it is added and never renumbered. Without a Job it is
|
|
18
|
+
// a draft; submitting binds the accepted Job, after which its state reads from the Shot's
|
|
19
|
+
// result pool. Failed versions keep their number; retrying means adding a new version.
|
|
20
|
+
const STAGES = ['t2i', 'i2v'];
|
|
21
|
+
const stageOf = (options) => {
|
|
22
|
+
const stage = options.stage ?? 'i2v';
|
|
23
|
+
if (!STAGES.includes(stage))
|
|
24
|
+
throw new CliError(2, 'INVALID_STAGE', '--stage must be t2i or i2v.');
|
|
25
|
+
return stage;
|
|
26
|
+
};
|
|
27
|
+
export const versionLabel = (version) =>
|
|
28
|
+
`${version.stage === 'i2v' ? 'T' : 'V'}${version.number}`;
|
|
29
|
+
export function versionSummary(shot, version) {
|
|
30
|
+
const row = version.job_id
|
|
31
|
+
? (shot.results.find((item) => item.job_id === version.job_id) ?? null)
|
|
32
|
+
: null;
|
|
33
|
+
return {
|
|
34
|
+
version_id: version.id,
|
|
35
|
+
version: versionLabel(version),
|
|
36
|
+
number: version.number,
|
|
37
|
+
stage: version.stage,
|
|
38
|
+
state: version.job_id === null ? 'draft' : row ? row.status : 'removed',
|
|
39
|
+
job_id: version.job_id,
|
|
40
|
+
result_id: row?.id ?? null,
|
|
41
|
+
has_output: Boolean(row?.output_url),
|
|
42
|
+
has_input: version.input !== null,
|
|
43
|
+
based_on_version_id: version.based_on_version_id,
|
|
44
|
+
updated_at: version.updated_at,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
async function shotOf(client, options) {
|
|
48
|
+
const creativeId = need(options.creative, '--creative');
|
|
49
|
+
const creative = await client.get(`/storyboards/${id(creativeId)}`);
|
|
50
|
+
const shotId = Number(need(options.shot, '--shot'));
|
|
51
|
+
const shot = creative.shots.find((row) => row.id === shotId);
|
|
52
|
+
if (!shot)
|
|
53
|
+
throw new CliError(
|
|
54
|
+
2,
|
|
55
|
+
'TARGET_MISMATCH',
|
|
56
|
+
'The Shot does not belong to this Creative.',
|
|
57
|
+
);
|
|
58
|
+
return { creativeId, shotId, shot };
|
|
59
|
+
}
|
|
60
|
+
function versionOf(shot, stage, options, flag = 'number') {
|
|
61
|
+
const number = Number(need(options[flag], `--${flag} (version number)`));
|
|
62
|
+
if (!Number.isInteger(number) || number < 1)
|
|
63
|
+
throw new CliError(
|
|
64
|
+
2,
|
|
65
|
+
'INVALID_VERSION_NUMBER',
|
|
66
|
+
`--${flag} is the number shown as T4 or V4, so 4.`,
|
|
67
|
+
);
|
|
68
|
+
const version = shot.versions.find(
|
|
69
|
+
(row) => row.stage === stage && row.number === number,
|
|
70
|
+
);
|
|
71
|
+
if (!version)
|
|
72
|
+
throw new CliError(
|
|
73
|
+
2,
|
|
74
|
+
'VERSION_NOT_FOUND',
|
|
75
|
+
`Shot ${shot.id} has no ${stage} version ${number}. Run version list.`,
|
|
76
|
+
);
|
|
77
|
+
return version;
|
|
78
|
+
}
|
|
79
|
+
/** The submission body is the saved input without the web editor state. */
|
|
80
|
+
export function submissionOf(input) {
|
|
81
|
+
const { editor: _editor, ...body } = input;
|
|
82
|
+
return body;
|
|
83
|
+
}
|
|
84
|
+
export async function writeVersionInput(client, shotId, versionId, input) {
|
|
85
|
+
const saved = await client.request(
|
|
86
|
+
'PUT',
|
|
87
|
+
`/shots/${id(shotId)}/versions/${id(versionId)}/input`,
|
|
88
|
+
{ input },
|
|
89
|
+
);
|
|
90
|
+
if (digest(saved.input) !== digest(input))
|
|
91
|
+
throw new CliError(
|
|
92
|
+
4,
|
|
93
|
+
'DRAFT_READBACK_DIFFERS',
|
|
94
|
+
'The saved draft differs from the supplied input. No generation was submitted.',
|
|
95
|
+
);
|
|
96
|
+
return saved;
|
|
97
|
+
}
|
|
98
|
+
async function present(value, summary, options) {
|
|
99
|
+
if (options.out) {
|
|
100
|
+
await writeJson(options.out, value, true);
|
|
101
|
+
return { ...summary, file: path.resolve(options.out) };
|
|
102
|
+
}
|
|
103
|
+
return options.full ? value : summary;
|
|
104
|
+
}
|
|
105
|
+
export async function version(action, subaction, options, context) {
|
|
106
|
+
const { client } = context;
|
|
107
|
+
if (action === 'list') {
|
|
108
|
+
const { creativeId, shotId, shot } = await shotOf(client, options);
|
|
109
|
+
const stage = options.stage === undefined ? null : stageOf(options);
|
|
110
|
+
const rows = shot.versions.filter((row) => !stage || row.stage === stage);
|
|
111
|
+
return {
|
|
112
|
+
creative_id: creativeId,
|
|
113
|
+
shot_id: shotId,
|
|
114
|
+
versions: options.full
|
|
115
|
+
? rows
|
|
116
|
+
: rows.map((row) => versionSummary(shot, row)),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (action === 'new') {
|
|
120
|
+
const { creativeId, shotId, shot } = await shotOf(client, options);
|
|
121
|
+
const stage = stageOf(options);
|
|
122
|
+
const base =
|
|
123
|
+
options.from === undefined
|
|
124
|
+
? null
|
|
125
|
+
: versionOf(shot, stage, options, 'from');
|
|
126
|
+
let input = options['request-file']
|
|
127
|
+
? await readJson(options['request-file'])
|
|
128
|
+
: null;
|
|
129
|
+
if (!input && base) {
|
|
130
|
+
if (base.input === null)
|
|
131
|
+
throw new CliError(
|
|
132
|
+
2,
|
|
133
|
+
'VERSION_INPUT_UNAVAILABLE',
|
|
134
|
+
`${versionLabel(base)} was generated before drafts were kept per version. Read its take with creative show --full and start the draft with --request-file.`,
|
|
135
|
+
);
|
|
136
|
+
input = base.input;
|
|
137
|
+
}
|
|
138
|
+
const created = await client.post(`/shots/${id(shotId)}/versions`, {
|
|
139
|
+
stage,
|
|
140
|
+
based_on_version_id: base?.id ?? null,
|
|
141
|
+
input,
|
|
142
|
+
});
|
|
143
|
+
const current = await client.get(`/storyboards/${id(creativeId)}`);
|
|
144
|
+
const shotNow = current.shots.find((row) => row.id === shotId);
|
|
145
|
+
const row = shotNow?.versions.find((item) => item.id === created.id);
|
|
146
|
+
if (!row)
|
|
147
|
+
throw new CliError(
|
|
148
|
+
5,
|
|
149
|
+
'VERSION_READBACK_MISSING',
|
|
150
|
+
'The new version is not visible in the Creative. Inspect it with version list.',
|
|
151
|
+
);
|
|
152
|
+
return {
|
|
153
|
+
creative_id: creativeId,
|
|
154
|
+
shot_id: shotId,
|
|
155
|
+
...versionSummary(shotNow, row),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
if (action === 'draft' && subaction === 'get') {
|
|
159
|
+
const { creativeId, shotId, shot } = await shotOf(client, options);
|
|
160
|
+
const target = versionOf(shot, stageOf(options), options);
|
|
161
|
+
return present(
|
|
162
|
+
target.input,
|
|
163
|
+
{
|
|
164
|
+
creative_id: creativeId,
|
|
165
|
+
shot_id: shotId,
|
|
166
|
+
...versionSummary(shot, target),
|
|
167
|
+
prompt_characters:
|
|
168
|
+
typeof target.input?.prompt_text === 'string'
|
|
169
|
+
? target.input.prompt_text.length
|
|
170
|
+
: 0,
|
|
171
|
+
},
|
|
172
|
+
options,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
if (action === 'draft' && subaction === 'set') {
|
|
176
|
+
const { creativeId, shotId, shot } = await shotOf(client, options);
|
|
177
|
+
const target = versionOf(shot, stageOf(options), options);
|
|
178
|
+
if (target.job_id !== null)
|
|
179
|
+
throw new CliError(
|
|
180
|
+
2,
|
|
181
|
+
'VERSION_SUBMITTED',
|
|
182
|
+
`${versionLabel(target)} is already submitted; add a new version.`,
|
|
183
|
+
);
|
|
184
|
+
const input = await readJson(
|
|
185
|
+
need(options['request-file'], '--request-file (version draft input)'),
|
|
186
|
+
);
|
|
187
|
+
const saved = await writeVersionInput(client, shotId, target.id, input);
|
|
188
|
+
return present(
|
|
189
|
+
saved,
|
|
190
|
+
{
|
|
191
|
+
creative_id: creativeId,
|
|
192
|
+
shot_id: shotId,
|
|
193
|
+
...versionSummary(shot, saved),
|
|
194
|
+
input_sha256: digest(saved.input),
|
|
195
|
+
},
|
|
196
|
+
options,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
if (action === 'delete') {
|
|
200
|
+
const { creativeId, shotId, shot } = await shotOf(client, options);
|
|
201
|
+
const target = versionOf(shot, stageOf(options), options);
|
|
202
|
+
if (target.job_id !== null)
|
|
203
|
+
throw new CliError(
|
|
204
|
+
2,
|
|
205
|
+
'VERSION_SUBMITTED',
|
|
206
|
+
`${versionLabel(target)} is already submitted; only drafts can be deleted.`,
|
|
207
|
+
);
|
|
208
|
+
await client.request(
|
|
209
|
+
'DELETE',
|
|
210
|
+
`/shots/${id(shotId)}/versions/${id(target.id)}`,
|
|
211
|
+
);
|
|
212
|
+
const current = await client.get(`/storyboards/${id(creativeId)}`);
|
|
213
|
+
const shotNow = current.shots.find((row) => row.id === shotId);
|
|
214
|
+
if (shotNow?.versions.some((row) => row.id === target.id))
|
|
215
|
+
throw new CliError(
|
|
216
|
+
4,
|
|
217
|
+
'DELETE_READBACK_DIFFERS',
|
|
218
|
+
'The version is still listed after deletion. Inspect the Creative.',
|
|
219
|
+
);
|
|
220
|
+
return {
|
|
221
|
+
creative_id: creativeId,
|
|
222
|
+
shot_id: shotId,
|
|
223
|
+
version_id: target.id,
|
|
224
|
+
version: versionLabel(target),
|
|
225
|
+
deleted: true,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
if (action === 'submit') return submitVersion(options, context);
|
|
229
|
+
throw new CliError(
|
|
230
|
+
2,
|
|
231
|
+
'UNKNOWN_COMMAND',
|
|
232
|
+
'Use version list, new, draft get/set, submit, or delete.',
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
// Same receipt discipline as video submit: the exact body and key are frozen before POST,
|
|
236
|
+
// an unknown acceptance is reconciled with --resume, and a rejection is only retried
|
|
237
|
+
// explicitly. The receipt also records which version the Job binds to.
|
|
238
|
+
async function submitVersion(options, context) {
|
|
239
|
+
if (options['retry-rejected'] && !options.resume)
|
|
240
|
+
throw new CliError(
|
|
241
|
+
2,
|
|
242
|
+
'RESUME_REQUIRED',
|
|
243
|
+
'--retry-rejected requires --resume and the original receipt.',
|
|
244
|
+
);
|
|
245
|
+
const file = path.resolve(need(options.receipt, '--receipt'));
|
|
246
|
+
return withWriter(`${file}.lock`, async () => {
|
|
247
|
+
const owner = await subject(context.client);
|
|
248
|
+
let receipt;
|
|
249
|
+
if (await exists(file)) {
|
|
250
|
+
receipt = await readJson(file);
|
|
251
|
+
if (receipt.schema_version !== 1 || receipt.kind !== 'version_submit')
|
|
252
|
+
throw new CliError(
|
|
253
|
+
2,
|
|
254
|
+
'WRONG_RECEIPT',
|
|
255
|
+
'Expected a version 1 version-submit receipt.',
|
|
256
|
+
);
|
|
257
|
+
validateIntent(
|
|
258
|
+
receipt.request,
|
|
259
|
+
receipt.idempotency_key,
|
|
260
|
+
receipt.endpoint,
|
|
261
|
+
);
|
|
262
|
+
if (digest(receipt.request) !== receipt.request_sha256)
|
|
263
|
+
throw new CliError(
|
|
264
|
+
2,
|
|
265
|
+
'INTENT_CHANGED',
|
|
266
|
+
'The saved submission request changed.',
|
|
267
|
+
);
|
|
268
|
+
sameSubject(receipt, owner);
|
|
269
|
+
if (!options.resume)
|
|
270
|
+
throw new CliError(
|
|
271
|
+
2,
|
|
272
|
+
'RECEIPT_EXISTS',
|
|
273
|
+
'Use --resume to reconcile this fixed intent; a new submission needs a new receipt.',
|
|
274
|
+
);
|
|
275
|
+
if (receipt.accepted) {
|
|
276
|
+
receipt.observation = await observeVideo(context.client, receipt);
|
|
277
|
+
await writeJson(file, receipt);
|
|
278
|
+
return {
|
|
279
|
+
receipt: file,
|
|
280
|
+
version_id: receipt.version_id,
|
|
281
|
+
version: receipt.version,
|
|
282
|
+
...receipt.accepted,
|
|
283
|
+
observation: receipt.observation,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
if (receipt.state === 'rejected') {
|
|
287
|
+
if (!options['retry-rejected'])
|
|
288
|
+
throw new CliError(
|
|
289
|
+
4,
|
|
290
|
+
'INTENT_REJECTED',
|
|
291
|
+
'Review the rejection before explicitly retrying this unchanged intent with --resume --retry-rejected.',
|
|
292
|
+
{ receipt: file },
|
|
293
|
+
);
|
|
294
|
+
receipt.rejected_attempts = [
|
|
295
|
+
...(receipt.rejected_attempts || []),
|
|
296
|
+
{ observed_at: new Date().toISOString(), error: receipt.error },
|
|
297
|
+
];
|
|
298
|
+
}
|
|
299
|
+
} else {
|
|
300
|
+
if (options.resume)
|
|
301
|
+
throw new CliError(
|
|
302
|
+
2,
|
|
303
|
+
'RECEIPT_NOT_FOUND',
|
|
304
|
+
'Resume requires the existing receipt file.',
|
|
305
|
+
);
|
|
306
|
+
const { creativeId, shotId, shot } = await shotOf(
|
|
307
|
+
context.client,
|
|
308
|
+
options,
|
|
309
|
+
);
|
|
310
|
+
const stage = stageOf(options);
|
|
311
|
+
const target = versionOf(shot, stage, options);
|
|
312
|
+
if (target.job_id !== null)
|
|
313
|
+
throw new CliError(
|
|
314
|
+
2,
|
|
315
|
+
'VERSION_SUBMITTED',
|
|
316
|
+
`${versionLabel(target)} is already submitted; add a new version.`,
|
|
317
|
+
);
|
|
318
|
+
const body = options['request-file']
|
|
319
|
+
? await readJson(options['request-file'])
|
|
320
|
+
: target.input === null
|
|
321
|
+
? null
|
|
322
|
+
: submissionOf(target.input);
|
|
323
|
+
if (!body)
|
|
324
|
+
throw new CliError(
|
|
325
|
+
2,
|
|
326
|
+
'DRAFT_EMPTY',
|
|
327
|
+
'This version has no saved draft. Write one with version draft set, or pass --request-file.',
|
|
328
|
+
);
|
|
329
|
+
const request = { stage, shot_id: shotId, ...body };
|
|
330
|
+
const key = options['idempotency-key'] || randomUUID();
|
|
331
|
+
const endpoint = `/shots/${shotId}/versions/${target.id}/submit`;
|
|
332
|
+
validateIntent(request, key, endpoint);
|
|
333
|
+
receipt = {
|
|
334
|
+
schema_version: 1,
|
|
335
|
+
kind: 'version_submit',
|
|
336
|
+
...owner,
|
|
337
|
+
creative_id: creativeId,
|
|
338
|
+
version_id: target.id,
|
|
339
|
+
version: versionLabel(target),
|
|
340
|
+
endpoint,
|
|
341
|
+
idempotency_key: key,
|
|
342
|
+
request,
|
|
343
|
+
request_sha256: digest(request),
|
|
344
|
+
state: 'prepared',
|
|
345
|
+
created_at: new Date().toISOString(),
|
|
346
|
+
};
|
|
347
|
+
await writeJson(file, receipt, true);
|
|
348
|
+
}
|
|
349
|
+
await acceptVideo(context.client, receipt, file);
|
|
350
|
+
return {
|
|
351
|
+
receipt: file,
|
|
352
|
+
version_id: receipt.version_id,
|
|
353
|
+
version: receipt.version,
|
|
354
|
+
...receipt.accepted,
|
|
355
|
+
observation: receipt.observation,
|
|
356
|
+
};
|
|
357
|
+
});
|
|
358
|
+
}
|