@uzooai/anime-ai-cli 0.0.0-stage → 0.5.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/.agents/skills/uzoo-anime-cli/SKILL.md +83 -0
- package/.agents/skills/uzoo-anime-cli/references/agreement-workflow.md +51 -0
- package/.agents/skills/uzoo-anime-cli/references/canvas-workflow.md +53 -0
- package/.agents/skills/uzoo-anime-cli/references/commands.md +250 -0
- package/.agents/skills/uzoo-anime-cli/references/generation-workflow.md +152 -0
- package/.agents/skills/uzoo-anime-cli/references/image-tools-workflow.md +175 -0
- package/.agents/skills/uzoo-anime-cli/references/interaction-workflow.md +67 -0
- package/.agents/skills/uzoo-anime-cli/references/material-graph-workflow.md +123 -0
- package/.agents/skills/uzoo-anime-cli/references/node-capabilities.md +95 -0
- package/.agents/skills/uzoo-anime-cli/references/node-workflow.md +94 -0
- package/.agents/skills/uzoo-anime-cli/references/remake-workflow.md +54 -0
- package/.agents/skills/uzoo-anime-cli/references/repaint-workflow.md +300 -0
- package/.agents/skills/uzoo-anime-cli/references/seedance-review-workflow.md +41 -0
- package/.agents/skills/uzoo-anime-cli/references/video-tools-workflow.md +71 -0
- package/CHANGELOG.md +92 -0
- package/LICENSE +21 -0
- package/README.md +242 -3
- package/assets/README.md +4 -0
- package/assets/favicon-uzoo.png +0 -0
- package/assets/logo-uzoo.svg +7 -0
- package/docs/CLI/345/275/223/345/211/215/350/214/203/345/233/264/344/270/216/346/234/254/345/234/260/346/211/223/345/214/205-20261103.md +134 -0
- package/docs/CLI/346/216/210/346/235/203/346/216/245/345/217/243/344/270/216/344/273/244/347/211/214/350/207/252/345/212/250/345/210/267/346/226/260/350/256/276/350/256/241.md +540 -0
- package/docs/CLI/347/274/272/345/217/243/346/240/270/345/257/271/344/270/216/346/224/266/345/260/276-20261105.md +65 -0
- package/docs/CLI/347/275/221/351/241/265/344/277/235/345/255/230/350/212/202/347/202/271/347/274/272/345/244/261/344/272/244/346/216/245.md +52 -0
- package/docs/M0-M5/345/256/236/346/226/275/344/270/216/351/252/214/346/224/266.md +89 -0
- package/docs/npm/347/272/277/344/270/212/345/217/221/345/270/203/344/270/216/345/256/211/350/243/205/344/275/277/347/224/250.md +151 -0
- package/docs//344/270/213/350/275/275/346/216/245/345/217/243/351/200/202/351/205/215/344/270/216/351/252/214/346/224/266.md +107 -0
- package/docs//344/270/232/345/212/241/350/203/275/345/212/233/350/241/245/345/205/250/344/270/216Skill/347/273/223/346/236/204/350/247/204/345/210/222.md +311 -0
- package/docs//345/205/250/345/261/200/351/231/204/344/273/266/350/265/204/346/272/220/345/234/260/345/235/200/344/270/216/346/250/241/345/236/213/350/257/273/345/217/226/345/267/256/345/274/202.md +47 -0
- package/docs//345/206/205/351/203/250Windows/345/256/211/350/243/205/344/270/216/345/215/207/347/272/247/351/252/214/346/224/266/346/270/205/345/215/225.md +34 -0
- package/docs//345/233/276/347/211/207/346/212/240/345/233/276/346/216/245/345/205/245/344/270/216/351/252/214/346/224/266-20261104.md +110 -0
- package/docs//345/274/200/345/217/221/350/256/241/345/210/222.md +127 -0
- package/docs//345/274/202/346/255/245/346/212/240/345/233/276CLI/346/235/203/351/231/220/344/272/244/346/216/245-20261105.md +50 -0
- package/docs//345/274/202/346/255/245/346/212/240/345/233/276/347/234/237/345/256/236/351/252/214/346/224/266-20261105.md +44 -0
- package/docs//346/226/260/344/274/232/350/257/235/344/273/273/345/212/241/344/272/244/344/273/243-/351/230/266/346/256/2652A.md +79 -0
- package/docs//346/226/260/351/203/250/347/275/262/347/224/273/345/270/203/345/206/262/347/252/201/351/252/214/346/224/266-20261102.md +61 -0
- package/docs//346/231/256/351/200/232/346/265/217/350/247/210/345/231/250/347/231/273/345/275/225.md +53 -0
- package/docs//346/255/243/345/274/217/347/253/231/345/210/207/346/215/242/344/270/216/351/252/214/346/224/266-20261104.md +90 -0
- package/docs//347/224/273/345/270/203CLI/344/270/213/344/270/200/351/230/266/346/256/265/345/274/200/345/217/221/350/256/241/345/210/222.md +136 -0
- package/docs//347/224/273/345/270/203CLI/351/235/236/350/275/254/347/273/230/345/256/214/345/226/204/350/256/241/345/210/222.md +183 -0
- package/docs//347/224/273/345/270/203/350/203/275/345/212/233/345/245/221/347/272/246/346/240/270/345/257/271.md +59 -0
- package/docs//347/234/237/345/256/236/344/270/232/345/212/241/346/216/245/345/205/245/344/270/216/346/265/213/350/257/225.md +1004 -0
- package/docs//347/264/240/346/235/220/345/205/245/345/272/223/344/270/216/347/224/273/345/270/203/344/277/235/345/255/230/351/227/256/351/242/230/345/256/232/344/275/215.md +52 -0
- package/docs//351/230/266/346/256/2653B/351/252/214/346/224/266/344/272/244/346/216/245.md +44 -0
- package/docs//351/230/266/346/256/265/344/270/200/351/252/214/346/224/266.md +60 -0
- package/docs//351/235/236/350/275/254/347/273/230/346/214/201/347/273/255/345/256/214/345/226/204/344/270/216/351/252/214/346/224/266-20261103.md +76 -0
- package/docs//351/235/236/350/275/254/347/273/230/347/254/254/344/272/214/346/211/271/345/256/236/346/226/275/344/270/216/351/252/214/346/224/266.md +39 -0
- package/docs//351/235/236/350/275/254/347/273/230/350/241/245/345/205/205/346/235/241/344/273/266/351/252/214/346/224/266-20261103.md +113 -0
- package/docs//351/235/236/350/275/254/347/273/230/351/246/226/346/211/271/345/256/236/346/226/275/344/270/216/351/252/214/346/224/266.md +71 -0
- package/examples/matting.json +5 -0
- package/examples/real-business/image-request.template.json +7 -0
- package/examples/real-business/node-patch.template.json +11 -0
- package/extensions/uzoo-cli-login/manifest.json +9 -0
- package/extensions/uzoo-cli-login/popup.css +1 -0
- package/extensions/uzoo-cli-login/popup.html +14 -0
- package/extensions/uzoo-cli-login/popup.js +65 -0
- package/package.json +56 -6
- package/scripts/check-package-resources.mjs +56 -0
- package/scripts/check-release.mjs +19 -0
- package/scripts/pack-local.mjs +33 -0
- package/scripts/release-policy.mjs +22 -0
- package/src/agreement-page.js +62 -0
- package/src/agreement.js +419 -0
- package/src/annotation-media.js +386 -0
- package/src/api.js +168 -0
- package/src/browser-login.js +13 -0
- package/src/callback-page.js +44 -0
- package/src/canvas.js +105 -0
- package/src/cli-authorization.js +122 -0
- package/src/cli.js +459 -0
- package/src/download-name.js +17 -0
- package/src/draft-finalize.js +229 -0
- package/src/generation-graph.js +124 -0
- package/src/generation-input.js +472 -0
- package/src/graph-disconnect.js +89 -0
- package/src/graph.js +431 -0
- package/src/group-edit.js +53 -0
- package/src/group-layout.js +113 -0
- package/src/group-membership.js +126 -0
- package/src/history-add.js +119 -0
- package/src/image-annotation.js +246 -0
- package/src/image-crop.js +283 -0
- package/src/image-matting.js +82 -0
- package/src/image-media.js +89 -0
- package/src/image-output.js +385 -0
- package/src/local-output.js +23 -0
- package/src/material-download.js +55 -0
- package/src/material-save.js +254 -0
- package/src/materials.js +147 -0
- package/src/matting-background.js +73 -0
- package/src/matting-jobs.js +63 -0
- package/src/matting-worker.js +49 -0
- package/src/matting.js +170 -0
- package/src/media-copy.js +53 -0
- package/src/media.js +343 -0
- package/src/model-options.js +76 -0
- package/src/multipart.js +28 -0
- package/src/node-copy.js +95 -0
- package/src/node-delete.js +49 -0
- package/src/node-history.js +231 -0
- package/src/normal-login.js +118 -0
- package/src/package-info.js +8 -0
- package/src/png-alpha.js +60 -0
- package/src/protocol.js +83 -0
- package/src/remake.js +738 -0
- package/src/repaint-archive.js +53 -0
- package/src/repaint-delivery.js +783 -0
- package/src/repaint-export.js +63 -0
- package/src/repaint-graph.js +214 -0
- package/src/repaint-history.js +267 -0
- package/src/repaint-materials.js +601 -0
- package/src/repaint-reverse.js +614 -0
- package/src/repaint-sequence.js +132 -0
- package/src/repaint-settings.js +576 -0
- package/src/repaint-video.js +798 -0
- package/src/repaint.js +326 -0
- package/src/resource.js +110 -0
- package/src/review.js +435 -0
- package/src/service.js +85 -0
- package/src/store.js +100 -0
- package/src/video-clip.js +381 -0
- package/src/video-frame.js +296 -0
- package/src/workflows.js +214 -0
package/src/agreement.js
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import crypto from 'node:crypto'
|
|
2
|
+
import http from 'node:http'
|
|
3
|
+
import { request, cancellation } from './api.js'
|
|
4
|
+
import { unwrap } from './protocol.js'
|
|
5
|
+
import { openLoginBrowser } from './normal-login.js'
|
|
6
|
+
import { agreementPage } from './agreement-page.js'
|
|
7
|
+
|
|
8
|
+
export const AGREEMENT_LABEL = '《统一协议》'
|
|
9
|
+
export const AGREEMENT_TITLE = '《Seedance2.0模型使用承诺书》'
|
|
10
|
+
export function agreementError(code, message, protocol) {
|
|
11
|
+
const error = new Error(message)
|
|
12
|
+
Object.assign(error, { code, apiCode: 4401, status: 'awaiting_agreement', protocol })
|
|
13
|
+
return error
|
|
14
|
+
}
|
|
15
|
+
export function validateProtocol(value) {
|
|
16
|
+
if (
|
|
17
|
+
!value ||
|
|
18
|
+
typeof value.required !== 'boolean' ||
|
|
19
|
+
typeof value.agreed !== 'boolean' ||
|
|
20
|
+
typeof value.version !== 'string'
|
|
21
|
+
)
|
|
22
|
+
throw new Error('协议状态响应不完整,停止继续生成')
|
|
23
|
+
return {
|
|
24
|
+
required: value.required,
|
|
25
|
+
agreed: value.agreed,
|
|
26
|
+
version: value.version,
|
|
27
|
+
label: AGREEMENT_LABEL,
|
|
28
|
+
document_title: AGREEMENT_TITLE,
|
|
29
|
+
message: String(value.message || '请阅读并确认协议后继续').slice(0, 2000)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export async function readProtocol(client) {
|
|
33
|
+
return validateProtocol((await client.raw('/video/protocol-agreement')).protocol)
|
|
34
|
+
}
|
|
35
|
+
async function publicText(url, timeout) {
|
|
36
|
+
const response = await request(
|
|
37
|
+
url,
|
|
38
|
+
{ headers: { Accept: 'text/html, application/json' } },
|
|
39
|
+
timeout
|
|
40
|
+
)
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
await response.body?.cancel()
|
|
43
|
+
throw new Error('协议正文读取失败')
|
|
44
|
+
}
|
|
45
|
+
const chunks = []
|
|
46
|
+
let size = 0
|
|
47
|
+
for await (const chunk of response.body) {
|
|
48
|
+
size += chunk.length
|
|
49
|
+
if (size > 2 * 1024 * 1024) {
|
|
50
|
+
await response.body.cancel().catch(() => {})
|
|
51
|
+
throw new Error('协议正文超过读取限制')
|
|
52
|
+
}
|
|
53
|
+
chunks.push(chunk)
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
text: Buffer.concat(chunks).toString('utf8'),
|
|
57
|
+
mime: response.headers.get('content-type') || ''
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export async function readAgreementDocument(client) {
|
|
61
|
+
const base = new URL(client.baseUrl)
|
|
62
|
+
const list = await publicText(client.baseUrl + '/content', client.timeout)
|
|
63
|
+
const items = unwrap(JSON.parse(list.text)).items
|
|
64
|
+
if (!Array.isArray(items)) throw new Error('已发布协议目录无效')
|
|
65
|
+
const matches = items.filter(
|
|
66
|
+
(i) => i.type === 'terms_of_service' && /seedance/i.test(i.title || '')
|
|
67
|
+
)
|
|
68
|
+
const exact = matches.filter(
|
|
69
|
+
(i) => String(i.title).replace(/\s/g, '') === 'Seedance2.0模型使用承诺书'
|
|
70
|
+
)
|
|
71
|
+
const candidates = exact.length ? exact : matches
|
|
72
|
+
if (candidates.length !== 1) throw new Error('未找到唯一的 Seedance 模型使用承诺书,停止签署')
|
|
73
|
+
const item = candidates[0]
|
|
74
|
+
let html = typeof item.content === 'string' ? item.content : ''
|
|
75
|
+
let source = client.baseUrl + '/content'
|
|
76
|
+
if (!html.trim()) {
|
|
77
|
+
const url = new URL(item.content_url, client.baseUrl + '/')
|
|
78
|
+
const prefix = base.pathname.replace(/\/$/, '')
|
|
79
|
+
if (
|
|
80
|
+
url.origin !== base.origin ||
|
|
81
|
+
url.username ||
|
|
82
|
+
url.password ||
|
|
83
|
+
url.hash ||
|
|
84
|
+
!new RegExp(
|
|
85
|
+
'^' + prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '/content/[0-9]+/body$'
|
|
86
|
+
).test(url.pathname) ||
|
|
87
|
+
url.searchParams.get('kind') !== 'doc' ||
|
|
88
|
+
[...url.searchParams.keys()].some((k) => k !== 'kind')
|
|
89
|
+
)
|
|
90
|
+
throw new Error('协议正文地址不属于当前服务的公开内容接口')
|
|
91
|
+
source = url.href
|
|
92
|
+
const content = await publicText(source, client.timeout)
|
|
93
|
+
if (!/text\/html/i.test(content.mime)) throw new Error('协议正文不是 HTML')
|
|
94
|
+
html = content.text
|
|
95
|
+
}
|
|
96
|
+
if (!html.trim() || html.length > 2 * 1024 * 1024) throw new Error('协议正文为空或过大,不能确认')
|
|
97
|
+
return {
|
|
98
|
+
id: item.id,
|
|
99
|
+
title: String(item.title),
|
|
100
|
+
version: item.version,
|
|
101
|
+
source,
|
|
102
|
+
html,
|
|
103
|
+
digest: crypto
|
|
104
|
+
.createHash('sha256')
|
|
105
|
+
.update(JSON.stringify([item.id, item.title, item.version, html]))
|
|
106
|
+
.digest('hex')
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const identity = (value) => {
|
|
110
|
+
const uid = String(value.uid ?? value.id ?? '')
|
|
111
|
+
if (!uid) throw new Error('无法核对协议签署账号')
|
|
112
|
+
return { uid, name: String(value.name || value.username || uid) }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function startAgreementBridge({
|
|
116
|
+
client,
|
|
117
|
+
protocol,
|
|
118
|
+
document,
|
|
119
|
+
account,
|
|
120
|
+
continuation,
|
|
121
|
+
timeout = 300000,
|
|
122
|
+
signal = cancellation.signal
|
|
123
|
+
}) {
|
|
124
|
+
const token = crypto.randomBytes(32).toString('base64url'),
|
|
125
|
+
csrf = crypto.randomBytes(32).toString('base64url'),
|
|
126
|
+
nonce = crypto.randomBytes(24).toString('base64url')
|
|
127
|
+
const pathname = '/agreement/' + token
|
|
128
|
+
let origin,
|
|
129
|
+
closed = false,
|
|
130
|
+
settled = false,
|
|
131
|
+
processing = false,
|
|
132
|
+
decisionSent = false,
|
|
133
|
+
timer,
|
|
134
|
+
resolveWait,
|
|
135
|
+
rejectWait
|
|
136
|
+
const wait = new Promise((resolve, reject) => {
|
|
137
|
+
resolveWait = resolve
|
|
138
|
+
rejectWait = reject
|
|
139
|
+
})
|
|
140
|
+
void wait.catch(() => {})
|
|
141
|
+
function fail(code, message, diagnostic) {
|
|
142
|
+
if (!settled) {
|
|
143
|
+
settled = true
|
|
144
|
+
const error=agreementError(code, message, protocol)
|
|
145
|
+
if(diagnostic){error.diagnostic=diagnostic;error.apiCode=diagnostic.api_code;error.requestId=diagnostic.request_id}
|
|
146
|
+
rejectWait(error)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function agreed(state) {
|
|
150
|
+
if (!settled) {
|
|
151
|
+
settled = true
|
|
152
|
+
resolveWait({
|
|
153
|
+
status: state.agreed ? 'agreed' : 'not_required',
|
|
154
|
+
protocol: state,
|
|
155
|
+
account: { uid: account.uid, name: account.name }
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async function checkAccount() {
|
|
160
|
+
if (identity(await client.raw('/auth/me')).uid !== account.uid)
|
|
161
|
+
throw agreementError(
|
|
162
|
+
'AGREEMENT_ACCOUNT_CHANGED',
|
|
163
|
+
'CLI 账号已变化,未继续签署或生成',
|
|
164
|
+
protocol
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
async function freshState() {
|
|
168
|
+
await checkAccount()
|
|
169
|
+
const state = await readProtocol(client)
|
|
170
|
+
if (state.version !== protocol.version)
|
|
171
|
+
throw agreementError(
|
|
172
|
+
'AGREEMENT_CHANGED',
|
|
173
|
+
'协议版本已更新,请重新打开并阅读当前版本后确认',
|
|
174
|
+
state
|
|
175
|
+
)
|
|
176
|
+
return state
|
|
177
|
+
}
|
|
178
|
+
async function freshDocument() {
|
|
179
|
+
const latest = await readAgreementDocument(client)
|
|
180
|
+
if (latest.digest !== document.digest)
|
|
181
|
+
throw agreementError('AGREEMENT_CHANGED', '协议正文已变化,请重新打开并阅读后确认', protocol)
|
|
182
|
+
}
|
|
183
|
+
const server = http.createServer(async (req, res) => {
|
|
184
|
+
res.setHeader('Cache-Control', 'no-store')
|
|
185
|
+
res.setHeader('Referrer-Policy', 'no-referrer')
|
|
186
|
+
res.setHeader('X-Content-Type-Options', 'nosniff')
|
|
187
|
+
const send = (status, data) => {
|
|
188
|
+
if (!res.writableEnded) {
|
|
189
|
+
res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' })
|
|
190
|
+
res.end(JSON.stringify(data))
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (closed || req.headers.host !== new URL(origin).host) {
|
|
194
|
+
send(403, { status: 'rejected', message: '本机确认地址不匹配' })
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
if (req.method === 'GET' && req.url === pathname) {
|
|
198
|
+
res.setHeader(
|
|
199
|
+
'Content-Security-Policy',
|
|
200
|
+
`default-src 'none'; script-src 'nonce-${nonce}'; style-src 'unsafe-inline'; frame-src 'self'; connect-src 'self'; form-action 'none'; frame-ancestors 'none'; base-uri 'none'`
|
|
201
|
+
)
|
|
202
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
|
|
203
|
+
res.end(
|
|
204
|
+
agreementPage({
|
|
205
|
+
protocol,
|
|
206
|
+
document,
|
|
207
|
+
account: { ...account, origin: new URL(client.baseUrl).origin },
|
|
208
|
+
continuation,
|
|
209
|
+
actionPath: pathname + '/decision',
|
|
210
|
+
csrf,
|
|
211
|
+
nonce
|
|
212
|
+
})
|
|
213
|
+
)
|
|
214
|
+
return
|
|
215
|
+
}
|
|
216
|
+
if (req.method === 'GET' && req.url === pathname + '/status') {
|
|
217
|
+
if (settled) {
|
|
218
|
+
send(200, { status: 'cancelled', message: '本次页面已结束,请查看 CLI 状态' })
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
if (processing) {
|
|
222
|
+
send(200, { status: 'waiting', message: '正在确认' })
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
const state = await freshState()
|
|
227
|
+
if (state.agreed || !state.required) {
|
|
228
|
+
if (state.required) await freshDocument()
|
|
229
|
+
res.once('finish', () => agreed(state))
|
|
230
|
+
send(200, {
|
|
231
|
+
status: state.agreed ? 'agreed' : 'not_required',
|
|
232
|
+
message: state.agreed
|
|
233
|
+
? '当前账号已确认当前版本协议,CLI 将核验后继续'
|
|
234
|
+
: '当前服务不要求协议确认,未记录同意,CLI 将重新核验后继续'
|
|
235
|
+
})
|
|
236
|
+
} else send(200, { status: 'waiting', protocol: state })
|
|
237
|
+
} catch (error) {
|
|
238
|
+
if (error.code?.startsWith('AGREEMENT_')) {
|
|
239
|
+
send(409, { status: 'changed', message: error.message })
|
|
240
|
+
fail(error.code, error.message)
|
|
241
|
+
} else send(503, { status: 'waiting', message: '协议状态查询暂不可用,尚未继续生成' })
|
|
242
|
+
}
|
|
243
|
+
return
|
|
244
|
+
}
|
|
245
|
+
if (req.method !== 'POST' || req.url !== pathname + '/decision') {
|
|
246
|
+
send(404, { status: 'rejected', message: '页面不存在' })
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
if (
|
|
250
|
+
req.headers.origin !== origin ||
|
|
251
|
+
req.headers['x-uzoo-agreement'] !== csrf ||
|
|
252
|
+
!/^application\/json(?:;|$)/i.test(req.headers['content-type'] || '')
|
|
253
|
+
) {
|
|
254
|
+
send(403, { status: 'rejected', message: '需要从本页明确确认' })
|
|
255
|
+
return
|
|
256
|
+
}
|
|
257
|
+
if (settled || processing || decisionSent) {
|
|
258
|
+
send(409, { status: 'waiting', message: '本次确认已处理或结果待核实,请勿重复点击' })
|
|
259
|
+
return
|
|
260
|
+
}
|
|
261
|
+
processing = true
|
|
262
|
+
try {
|
|
263
|
+
const chunks = []
|
|
264
|
+
let size = 0
|
|
265
|
+
for await (const chunk of req) {
|
|
266
|
+
size += chunk.length
|
|
267
|
+
if (size > 1024) {
|
|
268
|
+
send(413, { status: 'rejected', message: '确认请求过大' })
|
|
269
|
+
return
|
|
270
|
+
}
|
|
271
|
+
chunks.push(chunk)
|
|
272
|
+
}
|
|
273
|
+
let body
|
|
274
|
+
try {
|
|
275
|
+
body = JSON.parse(Buffer.concat(chunks).toString('utf8'))
|
|
276
|
+
} catch {
|
|
277
|
+
send(400, { status: 'rejected', message: '确认请求无效' })
|
|
278
|
+
return
|
|
279
|
+
}
|
|
280
|
+
if (!body || typeof body.agreed !== 'boolean' || Object.keys(body).length !== 1) {
|
|
281
|
+
send(400, { status: 'rejected', message: '缺少明确选择' })
|
|
282
|
+
return
|
|
283
|
+
}
|
|
284
|
+
if (!body.agreed) {
|
|
285
|
+
res.once('finish', () =>
|
|
286
|
+
fail(
|
|
287
|
+
'AGREEMENT_DECLINED',
|
|
288
|
+
'用户未同意《统一协议》《Seedance2.0模型使用承诺书》,本次生成未继续'
|
|
289
|
+
)
|
|
290
|
+
)
|
|
291
|
+
send(200, { status: 'declined', message: '你选择了不同意,本次生成未继续' })
|
|
292
|
+
return
|
|
293
|
+
}
|
|
294
|
+
const state = await freshState()
|
|
295
|
+
if (state.required) await freshDocument()
|
|
296
|
+
if (!state.agreed && state.required) {
|
|
297
|
+
decisionSent = true
|
|
298
|
+
// This authenticated POST only occurs after the human's checkbox + click.
|
|
299
|
+
await client.raw(
|
|
300
|
+
'/video/protocol-agreement',
|
|
301
|
+
{ method: 'POST', body: { agreed: true } },
|
|
302
|
+
{ retry: false }
|
|
303
|
+
)
|
|
304
|
+
}
|
|
305
|
+
const verified = await freshState()
|
|
306
|
+
if (verified.required) await freshDocument()
|
|
307
|
+
if (verified.required && !verified.agreed) throw new Error('签署记录尚未确认')
|
|
308
|
+
res.once('finish', () => agreed(verified))
|
|
309
|
+
send(200, {
|
|
310
|
+
status: verified.agreed ? 'agreed' : 'not_required',
|
|
311
|
+
message: verified.agreed
|
|
312
|
+
? '协议状态已核验,CLI 将检查原任务参数与费用后继续'
|
|
313
|
+
: '当前服务不要求协议确认,未记录同意,CLI 将重新核验后继续'
|
|
314
|
+
})
|
|
315
|
+
} catch (error) {
|
|
316
|
+
if (error.code === 'API_ERROR' && (error.apiCode === 2001 || [401,403].includes(error.status))) {
|
|
317
|
+
const requestId=/^[a-zA-Z0-9_-]{1,120}$/.test(error.requestId||'')?error.requestId:undefined
|
|
318
|
+
const message='协议确认被服务端拒绝(HTTP '+error.status+(error.apiCode?',code '+error.apiCode:'')+(requestId?',request_id '+requestId:'')+');尚未核验签署成功,未继续生成。请核对账号和接口权限。'
|
|
319
|
+
res.once('finish',()=>fail('AGREEMENT_REJECTED',message,{http_status:error.status,...(error.apiCode?{api_code:error.apiCode}:{}),...(requestId?{request_id:requestId}:{})}))
|
|
320
|
+
send(403,{status:'rejected',message})
|
|
321
|
+
} else
|
|
322
|
+
if (error.code?.startsWith('AGREEMENT_')) {
|
|
323
|
+
send(409, { status: 'changed', message: error.message })
|
|
324
|
+
fail(error.code, error.message)
|
|
325
|
+
} else
|
|
326
|
+
send(503, {
|
|
327
|
+
status: 'waiting',
|
|
328
|
+
message: '协议签署结果尚未确认,CLI 会只读查询;不会重复提交同意或继续生成'
|
|
329
|
+
})
|
|
330
|
+
} finally {
|
|
331
|
+
processing = false
|
|
332
|
+
}
|
|
333
|
+
})
|
|
334
|
+
server.requestTimeout = 10000
|
|
335
|
+
server.headersTimeout = 10000
|
|
336
|
+
await new Promise((resolve, reject) => {
|
|
337
|
+
server.once('error', reject)
|
|
338
|
+
server.listen(0, '127.0.0.1', resolve)
|
|
339
|
+
})
|
|
340
|
+
origin = `http://127.0.0.1:${server.address().port}`
|
|
341
|
+
const abort = () => fail('AGREEMENT_CANCELLED', '协议确认已取消,原任务未继续')
|
|
342
|
+
signal.addEventListener('abort', abort, { once: true })
|
|
343
|
+
if (signal.aborted) abort()
|
|
344
|
+
timer = setTimeout(
|
|
345
|
+
() =>
|
|
346
|
+
fail(
|
|
347
|
+
'AGREEMENT_WAIT_TIMEOUT',
|
|
348
|
+
'等待用户确认协议超时,原任务保持等待签署,可恢复原 operation'
|
|
349
|
+
),
|
|
350
|
+
timeout
|
|
351
|
+
)
|
|
352
|
+
return {
|
|
353
|
+
url: origin + pathname,
|
|
354
|
+
wait,
|
|
355
|
+
close: async () => {
|
|
356
|
+
if (closed) return
|
|
357
|
+
closed = true
|
|
358
|
+
clearTimeout(timer)
|
|
359
|
+
signal.removeEventListener('abort', abort)
|
|
360
|
+
server.closeAllConnections()
|
|
361
|
+
await new Promise((resolve) => server.close(resolve))
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export async function browserAgreement(
|
|
367
|
+
client,
|
|
368
|
+
{
|
|
369
|
+
browser = 'default',
|
|
370
|
+
timeout = 300000,
|
|
371
|
+
openUrl = openLoginBrowser,
|
|
372
|
+
onReady = () => {},
|
|
373
|
+
onOpenError = () => {},
|
|
374
|
+
continuation,
|
|
375
|
+
signal = cancellation.signal
|
|
376
|
+
} = {}
|
|
377
|
+
) {
|
|
378
|
+
if (
|
|
379
|
+
!['default', 'msedge', 'chrome'].includes(browser) ||
|
|
380
|
+
!Number.isSafeInteger(timeout) ||
|
|
381
|
+
timeout < 1 ||
|
|
382
|
+
timeout > 1800000
|
|
383
|
+
)
|
|
384
|
+
throw new Error('协议浏览器或等待时间无效')
|
|
385
|
+
const account = identity(await client.raw('/auth/me'))
|
|
386
|
+
const protocol = await readProtocol(client)
|
|
387
|
+
if (!protocol.required || protocol.agreed)
|
|
388
|
+
return { status: protocol.agreed ? 'already_agreed' : 'not_required', protocol, account }
|
|
389
|
+
let document
|
|
390
|
+
try {
|
|
391
|
+
document = await readAgreementDocument(client)
|
|
392
|
+
} catch {
|
|
393
|
+
throw agreementError(
|
|
394
|
+
'AGREEMENT_DOCUMENT_UNAVAILABLE',
|
|
395
|
+
'《统一协议》《Seedance2.0模型使用承诺书》正文暂不可用,未签署也未继续生成',
|
|
396
|
+
protocol
|
|
397
|
+
)
|
|
398
|
+
}
|
|
399
|
+
const bridge = await startAgreementBridge({
|
|
400
|
+
client,
|
|
401
|
+
protocol,
|
|
402
|
+
document,
|
|
403
|
+
account,
|
|
404
|
+
continuation,
|
|
405
|
+
timeout,
|
|
406
|
+
signal
|
|
407
|
+
})
|
|
408
|
+
try {
|
|
409
|
+
onReady(bridge.url, { protocol, account, document: { title: document.title, id: document.id } })
|
|
410
|
+
try {
|
|
411
|
+
await openUrl(bridge.url, browser)
|
|
412
|
+
} catch (error) {
|
|
413
|
+
onOpenError(error.message)
|
|
414
|
+
}
|
|
415
|
+
return await bridge.wait
|
|
416
|
+
} finally {
|
|
417
|
+
await bridge.close()
|
|
418
|
+
}
|
|
419
|
+
}
|