@blockrun/franklin 3.21.8 → 3.22.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 +12 -0
- package/dist/agent/context.js +5 -3
- package/dist/content/image-pricing.d.ts +7 -1
- package/dist/content/image-pricing.js +40 -17
- package/dist/content/record-image.d.ts +1 -1
- package/dist/content/record-image.js +2 -2
- package/dist/tools/blockrun.js +13 -4
- package/dist/tools/imagegen.d.ts +22 -3
- package/dist/tools/imagegen.js +252 -88
- package/dist/tools/index.js +5 -1
- package/dist/tools/realface.d.ts +29 -0
- package/dist/tools/realface.js +263 -0
- package/dist/tools/surf.d.ts +22 -0
- package/dist/tools/surf.js +281 -0
- package/dist/tools/tool-categories.js +7 -0
- package/dist/tools/videogen.js +44 -1
- package/dist/tools/voice.d.ts +1 -0
- package/dist/tools/voice.js +40 -21
- package/package.json +1 -1
package/dist/tools/videogen.js
CHANGED
|
@@ -28,6 +28,13 @@ import { ModelClient } from '../agent/llm.js';
|
|
|
28
28
|
import { analyzeMediaRequest, renderProposalForAskUser } from '../agent/media-router.js';
|
|
29
29
|
import { recordUsage } from '../stats/tracker.js';
|
|
30
30
|
import { findModel, estimateCostUsd } from '../gateway-models.js';
|
|
31
|
+
// BytePlus RealFace asset IDs from the RealFace tool (after H5 liveness +
|
|
32
|
+
// enrollment). Format: `ta_` + alphanumeric.
|
|
33
|
+
const REAL_FACE_ASSET_ID_REGEX = /^ta_[A-Za-z0-9]+$/;
|
|
34
|
+
const REAL_FACE_MODELS = new Set([
|
|
35
|
+
'bytedance/seedance-2.0',
|
|
36
|
+
'bytedance/seedance-2.0-fast',
|
|
37
|
+
]);
|
|
31
38
|
const DEFAULT_MODEL = 'xai/grok-imagine-video';
|
|
32
39
|
const DEFAULT_DURATION = 8;
|
|
33
40
|
const PRICE_PER_SECOND_USD = 0.05;
|
|
@@ -44,9 +51,33 @@ function estimateVideoCostUsd(durationSeconds = DEFAULT_DURATION) {
|
|
|
44
51
|
function buildExecute(deps) {
|
|
45
52
|
return async function execute(input, ctx) {
|
|
46
53
|
const rawInput = input;
|
|
47
|
-
const { output_path, model, image_url, duration_seconds, contentId, aspect_ratio } = rawInput;
|
|
54
|
+
const { output_path, model, image_url, duration_seconds, contentId, aspect_ratio, real_face_asset_id } = rawInput;
|
|
48
55
|
if (!rawInput.prompt)
|
|
49
56
|
return { output: 'Error: prompt is required', isError: true };
|
|
57
|
+
// RealFace asset client-side validations (the gateway 400s on the same
|
|
58
|
+
// conditions but a local check is friendlier — and the rejected request
|
|
59
|
+
// doesn't burn an x402 round-trip).
|
|
60
|
+
if (real_face_asset_id !== undefined) {
|
|
61
|
+
if (typeof real_face_asset_id !== 'string' || !REAL_FACE_ASSET_ID_REGEX.test(real_face_asset_id)) {
|
|
62
|
+
return {
|
|
63
|
+
output: `Error: real_face_asset_id must match "ta_<alphanumeric>" (e.g. ta_abc123). Enroll one with the RealFace tool. Got: ${JSON.stringify(real_face_asset_id)}`,
|
|
64
|
+
isError: true,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const chosenModel = model || DEFAULT_MODEL;
|
|
68
|
+
if (!REAL_FACE_MODELS.has(chosenModel)) {
|
|
69
|
+
return {
|
|
70
|
+
output: `Error: real_face_asset_id is only supported on Seedance 2.0 variants (${[...REAL_FACE_MODELS].join(', ')}). Current model: ${chosenModel}.`,
|
|
71
|
+
isError: true,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (image_url) {
|
|
75
|
+
return {
|
|
76
|
+
output: 'Error: real_face_asset_id and image_url both seed the first frame — pick one. Drop image_url to use RealFace, or drop real_face_asset_id to use the image.',
|
|
77
|
+
isError: true,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
50
81
|
// Resolve image_url before sending. The gateway requires a URL (http(s)
|
|
51
82
|
// or data: URI), but agents naturally pass a local file path —
|
|
52
83
|
// verified 2026-05-04 in a live session: agent passed
|
|
@@ -162,6 +193,10 @@ function buildExecute(deps) {
|
|
|
162
193
|
// value, the 400 body surfaces via 3.15.45 diagnostic so the agent
|
|
163
194
|
// can drop the param and retry.
|
|
164
195
|
...(aspect_ratio ? { aspect_ratio } : {}),
|
|
196
|
+
// RealFace (BytePlus, Seedance 2.0 only) — seeds the first frame from
|
|
197
|
+
// a real-person asset for cross-frame character consistency. Client
|
|
198
|
+
// already validated the ID + model gate above; just pass through.
|
|
199
|
+
...(real_face_asset_id ? { real_face_asset_id } : {}),
|
|
165
200
|
});
|
|
166
201
|
const headers = {
|
|
167
202
|
'Content-Type': 'application/json',
|
|
@@ -502,6 +537,14 @@ export function createVideoGenCapability(deps = {}) {
|
|
|
502
537
|
'error body surfaces — drop the param and retry.',
|
|
503
538
|
},
|
|
504
539
|
contentId: { type: 'string', description: 'Optional Content id to attach and budget against.' },
|
|
540
|
+
real_face_asset_id: {
|
|
541
|
+
type: 'string',
|
|
542
|
+
description: 'Optional BytePlus RealFace asset id (format `ta_<alphanumeric>`) for cross-frame ' +
|
|
543
|
+
'character consistency from a real person. Enroll one with the RealFace tool ' +
|
|
544
|
+
'(init → phone liveness → enroll). Seedance 2.0 variants only (bytedance/seedance-2.0, ' +
|
|
545
|
+
'bytedance/seedance-2.0-fast). Mutually exclusive with image_url — both seed the ' +
|
|
546
|
+
'first frame; pick one.',
|
|
547
|
+
},
|
|
505
548
|
},
|
|
506
549
|
required: ['prompt'],
|
|
507
550
|
},
|
package/dist/tools/voice.d.ts
CHANGED
|
@@ -15,5 +15,6 @@
|
|
|
15
15
|
* x402 payment flow mirrors src/tools/exa.ts.
|
|
16
16
|
*/
|
|
17
17
|
import type { CapabilityHandler } from '../agent/types.js';
|
|
18
|
+
export declare function buildVoiceCallBody(input: Record<string, unknown>): Record<string, unknown>;
|
|
18
19
|
export declare const voiceCallCapability: CapabilityHandler;
|
|
19
20
|
export declare const voiceStatusCapability: CapabilityHandler;
|
package/dist/tools/voice.js
CHANGED
|
@@ -183,6 +183,34 @@ async function extractPaymentReq(response) {
|
|
|
183
183
|
return header;
|
|
184
184
|
}
|
|
185
185
|
// ─── Tools ─────────────────────────────────────────────────────────────────
|
|
186
|
+
export function buildVoiceCallBody(input) {
|
|
187
|
+
const body = {
|
|
188
|
+
to: input.to,
|
|
189
|
+
from: input.from,
|
|
190
|
+
task: input.task,
|
|
191
|
+
};
|
|
192
|
+
// The gateway validates additionalProperties: false - only forward known
|
|
193
|
+
// optional fields, don't echo back whatever the caller passed.
|
|
194
|
+
if (typeof input.voice === 'string')
|
|
195
|
+
body.voice = input.voice;
|
|
196
|
+
if (typeof input.max_duration === 'number')
|
|
197
|
+
body.max_duration = input.max_duration;
|
|
198
|
+
if (typeof input.language === 'string')
|
|
199
|
+
body.language = input.language;
|
|
200
|
+
if (typeof input.first_sentence === 'string')
|
|
201
|
+
body.first_sentence = input.first_sentence;
|
|
202
|
+
if (typeof input.wait_for_greeting === 'boolean')
|
|
203
|
+
body.wait_for_greeting = input.wait_for_greeting;
|
|
204
|
+
if (typeof input.voicemail_action === 'string')
|
|
205
|
+
body.voicemail_action = input.voicemail_action;
|
|
206
|
+
if (typeof input.voicemail_message === 'string')
|
|
207
|
+
body.voicemail_message = input.voicemail_message;
|
|
208
|
+
if (typeof input.interruption_threshold === 'number')
|
|
209
|
+
body.interruption_threshold = input.interruption_threshold;
|
|
210
|
+
if (typeof input.model === 'string')
|
|
211
|
+
body.model = input.model;
|
|
212
|
+
return body;
|
|
213
|
+
}
|
|
186
214
|
export const voiceCallCapability = {
|
|
187
215
|
spec: {
|
|
188
216
|
name: 'VoiceCall',
|
|
@@ -252,6 +280,17 @@ export const voiceCallCapability = {
|
|
|
252
280
|
description: 'The message to leave when voicemail_action is "leave_message" (≤1000 chars). ' +
|
|
253
281
|
'Voicemail is one-way — this is spoken once as a monologue, there is no back-and-forth.',
|
|
254
282
|
},
|
|
283
|
+
interruption_threshold: {
|
|
284
|
+
type: 'integer',
|
|
285
|
+
minimum: 50,
|
|
286
|
+
maximum: 500,
|
|
287
|
+
description: 'Milliseconds of recipient speech before the agent pauses to listen (50-500).',
|
|
288
|
+
},
|
|
289
|
+
model: {
|
|
290
|
+
type: 'string',
|
|
291
|
+
enum: ['base', 'enhanced', 'turbo'],
|
|
292
|
+
description: 'Call model tier to use for the conversation.',
|
|
293
|
+
},
|
|
255
294
|
},
|
|
256
295
|
required: ['to', 'from', 'task'],
|
|
257
296
|
},
|
|
@@ -264,27 +303,7 @@ export const voiceCallCapability = {
|
|
|
264
303
|
if (typeof input.task !== 'string' || input.task.length < 10) {
|
|
265
304
|
return { output: 'task required (10–4000 chars natural-language description)', isError: true };
|
|
266
305
|
}
|
|
267
|
-
const body =
|
|
268
|
-
to: input.to,
|
|
269
|
-
from: input.from,
|
|
270
|
-
task: input.task,
|
|
271
|
-
};
|
|
272
|
-
// The gateway validates additionalProperties: false — only forward known
|
|
273
|
-
// optional fields, don't echo back whatever the caller passed.
|
|
274
|
-
if (typeof input.voice === 'string')
|
|
275
|
-
body.voice = input.voice;
|
|
276
|
-
if (typeof input.max_duration === 'number')
|
|
277
|
-
body.max_duration = input.max_duration;
|
|
278
|
-
if (typeof input.language === 'string')
|
|
279
|
-
body.language = input.language;
|
|
280
|
-
if (typeof input.first_sentence === 'string')
|
|
281
|
-
body.first_sentence = input.first_sentence;
|
|
282
|
-
if (typeof input.wait_for_greeting === 'boolean')
|
|
283
|
-
body.wait_for_greeting = input.wait_for_greeting;
|
|
284
|
-
if (typeof input.voicemail_action === 'string')
|
|
285
|
-
body.voicemail_action = input.voicemail_action;
|
|
286
|
-
if (typeof input.voicemail_message === 'string')
|
|
287
|
-
body.voicemail_message = input.voicemail_message;
|
|
306
|
+
const body = buildVoiceCallBody(input);
|
|
288
307
|
try {
|
|
289
308
|
const res = await postWithPayment('/v1/voice/call', body, ctx, { tool: 'VoiceCall', priceUsd: 0.54 });
|
|
290
309
|
const callId = (res.call_id || res.id);
|
package/package.json
CHANGED