@blockrun/franklin 3.21.6 → 3.21.8
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/dist/agent/loop.js +9 -1
- package/dist/tools/prediction.js +173 -58
- package/dist/tools/videogen.js +1 -44
- package/package.json +1 -1
package/dist/agent/loop.js
CHANGED
|
@@ -46,7 +46,15 @@ import { createSessionId, appendToSession, updateSessionMeta, pruneOldSessions,
|
|
|
46
46
|
function replaceHistory(target, replacement) {
|
|
47
47
|
target.splice(0, target.length, ...replacement);
|
|
48
48
|
}
|
|
49
|
-
|
|
49
|
+
// 400/422 (malformed request / failed param validation) are added alongside
|
|
50
|
+
// 401/403/429/5xx: like an auth wall, retrying the same bad request never
|
|
51
|
+
// recovers — the agent must change its inputs, not hammer the endpoint. Caught
|
|
52
|
+
// 2026-05-20 when a `status=active` 422 (invalid enum, Predexon wants
|
|
53
|
+
// open|closed) spun PredictionMarket to the 50-call HARD_TOOL_CAP because the
|
|
54
|
+
// 422 was neither charged (cost guard idle) nor matched here (wall guard idle).
|
|
55
|
+
// 404 is intentionally excluded — "not found" is a legitimate cue to retry
|
|
56
|
+
// with a different query, not a dead wall.
|
|
57
|
+
const EXTERNAL_WALL_FAILURE_PATTERN = /\b(?:400|401|403|422|429|5\d{2})\b|\bunauthor|\bforbid|\bWAF\b|\bcloudflare\b|\bfault filter\b|\bblocked\b|\binvalid (?:auth|api|token|key|bearer)\b/i;
|
|
50
58
|
export function isExternalWallFailure(toolName, output, isError) {
|
|
51
59
|
if (toolName === 'WebFetch') {
|
|
52
60
|
return isError === true || EXTERNAL_WALL_FAILURE_PATTERN.test(output);
|
package/dist/tools/prediction.js
CHANGED
|
@@ -220,6 +220,8 @@ function unwrapList(raw) {
|
|
|
220
220
|
return obj.results;
|
|
221
221
|
if (Array.isArray(obj.positions))
|
|
222
222
|
return obj.positions;
|
|
223
|
+
if (Array.isArray(obj.entries))
|
|
224
|
+
return obj.entries; // leaderboard
|
|
223
225
|
}
|
|
224
226
|
return [];
|
|
225
227
|
}
|
|
@@ -229,6 +231,46 @@ function parseWalletsInput(value) {
|
|
|
229
231
|
.map(w => w.trim())
|
|
230
232
|
.filter(Boolean);
|
|
231
233
|
}
|
|
234
|
+
// Predexon's market-list endpoints (polymarket/markets, kalshi/markets,
|
|
235
|
+
// markets/search) all validate `status` against StatusOption = {open, closed}
|
|
236
|
+
// — verified against openapi-v2.json. Earlier code defaulted Polymarket to
|
|
237
|
+
// `active` (the Polymarket Gamma-API convention), which 422s on Predexon.
|
|
238
|
+
// Normalize the common synonyms so an explicit `active` / `archived` coming
|
|
239
|
+
// from the agent or the user still resolves to a valid value.
|
|
240
|
+
// Polymarket leaderboard `sort_by` enum (openapi-v2.json:
|
|
241
|
+
// realized_pnl | total_pnl | volume | roi | profit_factor | win_rate | trades).
|
|
242
|
+
// Maps the ergonomic values the tool advertises ("pnl | volume | win_rate")
|
|
243
|
+
// — and a few obvious synonyms — onto the real enum. Unknown keys resolve to
|
|
244
|
+
// undefined so the param is omitted and the gateway uses its own default.
|
|
245
|
+
const LEADERBOARD_SORT_ALIASES = {
|
|
246
|
+
pnl: 'total_pnl',
|
|
247
|
+
total_pnl: 'total_pnl',
|
|
248
|
+
realized_pnl: 'realized_pnl',
|
|
249
|
+
realized: 'realized_pnl',
|
|
250
|
+
volume: 'volume',
|
|
251
|
+
roi: 'roi',
|
|
252
|
+
profit_factor: 'profit_factor',
|
|
253
|
+
win_rate: 'win_rate',
|
|
254
|
+
winrate: 'win_rate',
|
|
255
|
+
trades: 'trades',
|
|
256
|
+
};
|
|
257
|
+
// smartActivity and smart-money both REQUIRE at least one smart-wallet
|
|
258
|
+
// criterion (min_realized_pnl / min_total_pnl / min_roi / …) or Predexon 400s
|
|
259
|
+
// with "At least one smart wallet criterion must be specified" — verified live
|
|
260
|
+
// 2026-05-20. The old code sent none, so both endpoints failed every call.
|
|
261
|
+
// Default to a sensible "smart = profitable" floor; the agent can still pass
|
|
262
|
+
// its own threshold via the `minTotalPnl` input.
|
|
263
|
+
const DEFAULT_SMART_MIN_TOTAL_PNL = 10000;
|
|
264
|
+
function normalizeMarketStatus(status) {
|
|
265
|
+
if (!status)
|
|
266
|
+
return status;
|
|
267
|
+
const s = status.trim().toLowerCase();
|
|
268
|
+
if (s === 'active' || s === 'open' || s === 'live')
|
|
269
|
+
return 'open';
|
|
270
|
+
if (s === 'closed' || s === 'archived' || s === 'resolved' || s === 'inactive')
|
|
271
|
+
return 'closed';
|
|
272
|
+
return s;
|
|
273
|
+
}
|
|
232
274
|
/**
|
|
233
275
|
* Pick the first usable string from a list of candidate values.
|
|
234
276
|
*
|
|
@@ -286,7 +328,7 @@ async function execute(input, ctx) {
|
|
|
286
328
|
// searchPolymarket / searchKalshi; rename on the wire.
|
|
287
329
|
const raw = await getWithPayment('/v1/pm/markets/search', {
|
|
288
330
|
q: search,
|
|
289
|
-
status,
|
|
331
|
+
status: normalizeMarketStatus(status),
|
|
290
332
|
sort,
|
|
291
333
|
limit: cappedLimit,
|
|
292
334
|
}, ctx);
|
|
@@ -313,7 +355,7 @@ async function execute(input, ctx) {
|
|
|
313
355
|
shown.forEach((m, i) => {
|
|
314
356
|
const title = pickString(m.title, m.question, m.market, m.event, m.market_slug, m.slug, m.ticker) ?? 'untitled';
|
|
315
357
|
const id = pickString(m.condition_id, m.ticker, m.id);
|
|
316
|
-
const idTag = id ? ` · \`${String(id)
|
|
358
|
+
const idTag = id ? ` · \`${String(id)}\`` : '';
|
|
317
359
|
const vol = m.volume != null ? ` · vol ${formatUsd(m.volume)}` : '';
|
|
318
360
|
lines.push(`${i + 1}. ${title}${idTag}${vol}`);
|
|
319
361
|
totalShown++;
|
|
@@ -350,9 +392,15 @@ async function execute(input, ctx) {
|
|
|
350
392
|
case 'leaderboard': {
|
|
351
393
|
// Global top-wallet ranking. Cheap ($0.001) — the right answer to
|
|
352
394
|
// "who's making money on Polymarket" / "who should I follow".
|
|
395
|
+
// Predexon's param is `sort_by` (enum), NOT `sort` — sending `sort`
|
|
396
|
+
// is silently ignored, so the ranking never honored the agent's
|
|
397
|
+
// intent. Map the ergonomic aliases to the real enum values
|
|
398
|
+
// (verified against openapi-v2.json), drop anything unrecognized so
|
|
399
|
+
// the gateway falls back to its own default rather than 422-ing.
|
|
400
|
+
const sortBy = sort ? LEADERBOARD_SORT_ALIASES[sort.trim().toLowerCase()] : undefined;
|
|
353
401
|
const raw = await getWithPayment('/v1/pm/polymarket/leaderboard', {
|
|
354
402
|
limit: cappedLimit,
|
|
355
|
-
|
|
403
|
+
sort_by: sortBy,
|
|
356
404
|
}, ctx);
|
|
357
405
|
const rows = unwrapList(raw);
|
|
358
406
|
if (rows.length === 0) {
|
|
@@ -363,13 +411,18 @@ async function execute(input, ctx) {
|
|
|
363
411
|
'',
|
|
364
412
|
];
|
|
365
413
|
rows.forEach((r, i) => {
|
|
366
|
-
|
|
414
|
+
// Predexon v2 entry: { rank, user, metrics:{ total_pnl, volume,
|
|
415
|
+
// win_rate, ... }, ... } — verified live 2026-05-20. P&L/volume/win
|
|
416
|
+
// live under `metrics`, the address is `user`; the old flat
|
|
417
|
+
// r.pnl / r.wallet reads returned undefined for every row.
|
|
418
|
+
const metrics = (r.metrics && typeof r.metrics === 'object' ? r.metrics : {});
|
|
419
|
+
const wallet = pickString(r.user, r.wallet, r.address, r.proxy_wallet, r.proxyWallet) ?? 'unknown';
|
|
367
420
|
const w = wallet.length > 12
|
|
368
421
|
? `${wallet.slice(0, 8)}…${wallet.slice(-4)}`
|
|
369
422
|
: wallet;
|
|
370
|
-
const pnl = r.pnl ?? r.realized_pnl ?? r.total_pnl;
|
|
371
|
-
const volume = r.volume ?? r.total_volume;
|
|
372
|
-
const winRate = r.win_rate ?? r.winRate;
|
|
423
|
+
const pnl = metrics.total_pnl ?? metrics.realized_pnl ?? r.pnl ?? r.realized_pnl ?? r.total_pnl;
|
|
424
|
+
const volume = metrics.volume ?? r.volume ?? r.total_volume;
|
|
425
|
+
const winRate = metrics.win_rate ?? r.win_rate ?? r.winRate;
|
|
373
426
|
const name = pickString(r.name, r.handle, r.username);
|
|
374
427
|
const handle = name ? ` (${name})` : '';
|
|
375
428
|
const parts = [];
|
|
@@ -645,9 +698,11 @@ async function execute(input, ctx) {
|
|
|
645
698
|
// "Discover markets where high-performing wallets are active right now."
|
|
646
699
|
// Complements `smartMoney`: this discovers interesting markets across
|
|
647
700
|
// the venue; smartMoney drills into one condition_id.
|
|
701
|
+
// Requires a smart-wallet criterion (else 400). `search` is not a
|
|
702
|
+
// supported param here, so it was silently dropped — removed.
|
|
648
703
|
const raw = await getWithPayment('/v1/pm/polymarket/markets/smart-activity', {
|
|
649
704
|
limit: cappedLimit,
|
|
650
|
-
|
|
705
|
+
min_total_pnl: DEFAULT_SMART_MIN_TOTAL_PNL,
|
|
651
706
|
}, ctx);
|
|
652
707
|
const rows = unwrapList(raw);
|
|
653
708
|
if (rows.length === 0) {
|
|
@@ -661,14 +716,20 @@ async function execute(input, ctx) {
|
|
|
661
716
|
rows.forEach((r, i) => {
|
|
662
717
|
const title = pickString(r.question, r.title, r.market, r.event, r.market_slug, r.slug) ?? 'untitled';
|
|
663
718
|
const cid = pickString(r.condition_id, r.id);
|
|
664
|
-
|
|
665
|
-
const
|
|
666
|
-
|
|
719
|
+
// Full condition_id so the agent can chain into smartMoney.
|
|
720
|
+
const cidTag = cid ? ` · \`${String(cid)}\`` : '';
|
|
721
|
+
// Predexon v2 fields (verified live): smart_wallet_count (singular),
|
|
722
|
+
// smart_volume, net_buyers_pct.
|
|
723
|
+
const smartCount = r.smart_wallet_count ?? r.smart_wallets_count ?? r.wallet_count;
|
|
724
|
+
const smartVol = r.smart_volume ?? r.net_size ?? r.net_yes_size;
|
|
725
|
+
const netBuyersPct = r.net_buyers_pct;
|
|
667
726
|
const stats = [];
|
|
668
727
|
if (smartCount != null)
|
|
669
728
|
stats.push(`${smartCount} smart wallet${smartCount === 1 ? '' : 's'}`);
|
|
670
|
-
if (
|
|
671
|
-
stats.push(`
|
|
729
|
+
if (smartVol != null)
|
|
730
|
+
stats.push(`smart vol ${formatUsd(smartVol)}`);
|
|
731
|
+
if (netBuyersPct != null)
|
|
732
|
+
stats.push(`${formatPct(netBuyersPct, 0)} net buyers`);
|
|
672
733
|
lines.push(`${i + 1}. **${title}**${cidTag}` + (stats.length > 0 ? `\n ${stats.join(' · ')}` : ''));
|
|
673
734
|
});
|
|
674
735
|
lines.push('', `_$0.005 paid via x402._`);
|
|
@@ -681,34 +742,50 @@ async function execute(input, ctx) {
|
|
|
681
742
|
isError: true,
|
|
682
743
|
};
|
|
683
744
|
}
|
|
684
|
-
// Per-market drill-down.
|
|
685
|
-
// /
|
|
745
|
+
// Per-market drill-down. Requires a smart-wallet criterion (else 400).
|
|
746
|
+
// Predexon v2 returns a single `positioning` aggregate (net buyer/
|
|
747
|
+
// seller counts + smart buy/sell volume + avg prices), NOT buyers/
|
|
748
|
+
// sellers arrays — verified live 2026-05-20.
|
|
686
749
|
const path = `/v1/pm/polymarket/market/${encodeURIComponent(conditionId)}/smart-money`;
|
|
687
|
-
const data = await getWithPayment(path, {
|
|
688
|
-
|
|
689
|
-
|
|
750
|
+
const data = await getWithPayment(path, {
|
|
751
|
+
min_total_pnl: DEFAULT_SMART_MIN_TOTAL_PNL,
|
|
752
|
+
}, ctx);
|
|
753
|
+
const pos = data.positioning;
|
|
754
|
+
if (!pos || typeof pos !== 'object') {
|
|
755
|
+
return { output: `No smart-money positioning recorded for \`${conditionId.slice(0, 14)}…\` yet.` };
|
|
756
|
+
}
|
|
690
757
|
const lines = [
|
|
691
|
-
`## Smart money —
|
|
758
|
+
`## Smart money — ${pos.title ?? `\`${conditionId.slice(0, 14)}…\``}`,
|
|
692
759
|
];
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
if (
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
if (
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
});
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
760
|
+
const head = [];
|
|
761
|
+
if (pos.smart_wallet_count != null)
|
|
762
|
+
head.push(`${pos.smart_wallet_count} smart wallets`);
|
|
763
|
+
if (pos.net_buyers != null && pos.net_sellers != null)
|
|
764
|
+
head.push(`${pos.net_buyers} buyers / ${pos.net_sellers} sellers`);
|
|
765
|
+
if (pos.net_buyers_pct != null)
|
|
766
|
+
head.push(`${formatPct(pos.net_buyers_pct, 0)} net buyers`);
|
|
767
|
+
if (head.length > 0)
|
|
768
|
+
lines.push(head.join(' · '));
|
|
769
|
+
const flow = [];
|
|
770
|
+
if (pos.total_smart_buy_volume != null)
|
|
771
|
+
flow.push(`buy ${formatUsd(pos.total_smart_buy_volume)}`);
|
|
772
|
+
if (pos.total_smart_sell_volume != null)
|
|
773
|
+
flow.push(`sell ${formatUsd(pos.total_smart_sell_volume)}`);
|
|
774
|
+
if (pos.avg_smart_buy_price != null)
|
|
775
|
+
flow.push(`avg buy ${formatPct(pos.avg_smart_buy_price)}`);
|
|
776
|
+
if (pos.avg_smart_sell_price != null)
|
|
777
|
+
flow.push(`avg sell ${formatPct(pos.avg_smart_sell_price)}`);
|
|
778
|
+
if (flow.length > 0)
|
|
779
|
+
lines.push('', `**Smart flow:** ${flow.join(' · ')}`);
|
|
780
|
+
if (pos.total_smart_realized_pnl != null || pos.avg_smart_roi != null) {
|
|
781
|
+
const perf = [];
|
|
782
|
+
if (pos.total_smart_realized_pnl != null)
|
|
783
|
+
perf.push(`realized P&L ${formatUsd(pos.total_smart_realized_pnl)}`);
|
|
784
|
+
if (pos.avg_smart_roi != null)
|
|
785
|
+
perf.push(`avg ROI ${formatPct(pos.avg_smart_roi, 1)}`);
|
|
786
|
+
if (pos.avg_smart_win_rate != null)
|
|
787
|
+
perf.push(`win ${formatPct(pos.avg_smart_win_rate, 0)}`);
|
|
788
|
+
lines.push(`**Smart performance:** ${perf.join(' · ')}`);
|
|
712
789
|
}
|
|
713
790
|
lines.push('', `_$0.005 paid via x402._`);
|
|
714
791
|
return { output: lines.join('\n') };
|
|
@@ -716,7 +793,7 @@ async function execute(input, ctx) {
|
|
|
716
793
|
case 'searchPolymarket': {
|
|
717
794
|
const raw = await getWithPayment('/v1/pm/polymarket/markets', {
|
|
718
795
|
search,
|
|
719
|
-
status: status ?? '
|
|
796
|
+
status: normalizeMarketStatus(status) ?? 'open',
|
|
720
797
|
sort: sort ?? 'volume',
|
|
721
798
|
limit: cappedLimit,
|
|
722
799
|
}, ctx);
|
|
@@ -732,13 +809,30 @@ async function execute(input, ctx) {
|
|
|
732
809
|
'',
|
|
733
810
|
];
|
|
734
811
|
markets.forEach((m, i) => {
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
812
|
+
// Prices: Predexon v2 nests each outcome as {label, price} inside
|
|
813
|
+
// `outcomes`. Fall back to the legacy parallel `outcomes[]` (strings)
|
|
814
|
+
// + `outcome_prices[]` shape if a gateway version still returns it.
|
|
815
|
+
let yesPx = 'n/a';
|
|
816
|
+
if (Array.isArray(m.outcomes) && m.outcomes.length > 0 && typeof m.outcomes[0] === 'object') {
|
|
817
|
+
const outs = m.outcomes;
|
|
818
|
+
const parts = outs
|
|
819
|
+
.filter(o => o && o.price != null)
|
|
820
|
+
.map(o => `${o.label ?? '?'}=${formatPct(o.price)}`);
|
|
821
|
+
if (parts.length > 0)
|
|
822
|
+
yesPx = parts.join(' / ');
|
|
823
|
+
}
|
|
824
|
+
else if (Array.isArray(m.outcomes) && Array.isArray(m.outcome_prices) && m.outcomes.length === m.outcome_prices.length) {
|
|
825
|
+
yesPx = m.outcomes.map((o, j) => `${o}=${formatPct(m.outcome_prices[j])}`).join(' / ');
|
|
826
|
+
}
|
|
827
|
+
const vol = m.total_volume_usd ?? m.volume;
|
|
828
|
+
const liq = m.liquidity_usd ?? m.liquidity;
|
|
829
|
+
const end = m.end_time ?? m.close_time ?? m.end_date;
|
|
830
|
+
// Full condition_id (NOT truncated) — the agent chains it into
|
|
831
|
+
// smartMoney; a truncated id 404s. Verified live 2026-05-20.
|
|
832
|
+
const cid = m.condition_id ? ` · condition_id=\`${m.condition_id}\`` : '';
|
|
833
|
+
lines.push(`${i + 1}. **${m.title || m.question || m.market_slug || 'untitled'}**${cid}\n` +
|
|
834
|
+
` prices: ${yesPx} · vol: ${formatUsd(vol)} · liq: ${formatUsd(liq)}` +
|
|
835
|
+
(end ? ` · ends ${String(end).slice(0, 10)}` : ''));
|
|
742
836
|
});
|
|
743
837
|
lines.push('', `_$0.001 paid via x402._`);
|
|
744
838
|
return { output: lines.join('\n') };
|
|
@@ -746,7 +840,7 @@ async function execute(input, ctx) {
|
|
|
746
840
|
case 'searchKalshi': {
|
|
747
841
|
const raw = await getWithPayment('/v1/pm/kalshi/markets', {
|
|
748
842
|
search,
|
|
749
|
-
status: status ?? 'open',
|
|
843
|
+
status: normalizeMarketStatus(status) ?? 'open',
|
|
750
844
|
sort: sort ?? 'volume',
|
|
751
845
|
limit: cappedLimit,
|
|
752
846
|
}, ctx);
|
|
@@ -761,14 +855,20 @@ async function execute(input, ctx) {
|
|
|
761
855
|
'',
|
|
762
856
|
];
|
|
763
857
|
markets.forEach((m, i) => {
|
|
764
|
-
//
|
|
765
|
-
//
|
|
766
|
-
|
|
767
|
-
|
|
858
|
+
// Predexon v2 gives a single `last_price` (0–1 probability), not the
|
|
859
|
+
// yes_bid/yes_ask cents this once assumed. Render it as an implied
|
|
860
|
+
// YES %; fall back to legacy bid/ask if a gateway version sends them.
|
|
861
|
+
let yes = 'n/a';
|
|
862
|
+
if (m.last_price != null) {
|
|
863
|
+
yes = formatPct(m.last_price);
|
|
864
|
+
}
|
|
865
|
+
else if (m.yes_bid != null || m.yes_ask != null) {
|
|
866
|
+
yes = `${m.yes_bid ?? '?'}¢/${m.yes_ask ?? '?'}¢`;
|
|
867
|
+
}
|
|
768
868
|
const ticker = m.ticker ? ` · ticker=\`${m.ticker}\`` : '';
|
|
769
869
|
lines.push(`${i + 1}. **${m.title || m.ticker || 'untitled'}**${ticker}\n` +
|
|
770
|
-
` yes ${
|
|
771
|
-
(m.close_time ? ` · closes ${m.close_time.slice(0, 10)}` : ''));
|
|
870
|
+
` yes ${yes} · vol: ${m.volume?.toLocaleString() ?? 'n/a'} · OI: ${m.open_interest?.toLocaleString() ?? 'n/a'}` +
|
|
871
|
+
(m.close_time ? ` · closes ${String(m.close_time).slice(0, 10)}` : ''));
|
|
772
872
|
});
|
|
773
873
|
lines.push('', `_$0.001 paid via x402._`);
|
|
774
874
|
return { output: lines.join('\n') };
|
|
@@ -787,11 +887,26 @@ async function execute(input, ctx) {
|
|
|
787
887
|
'',
|
|
788
888
|
];
|
|
789
889
|
pairs.forEach((p, i) => {
|
|
890
|
+
// Venues are nested UPPERCASE sub-objects in Predexon v2. pickString
|
|
891
|
+
// walks the sub-object's name keys (title/slug/...), so passing the
|
|
892
|
+
// whole `POLYMARKET` / `KALSHI` object resolves the title regardless
|
|
893
|
+
// of which name-bearing key is populated. Flat legacy fields are
|
|
894
|
+
// passed as fallbacks.
|
|
895
|
+
const poly = p.POLYMARKET ?? undefined;
|
|
896
|
+
const kalshi = p.KALSHI ?? undefined;
|
|
897
|
+
const polyTitle = pickString(poly, p.polymarket_question) ?? '(untitled)';
|
|
898
|
+
const kalshiTitle = pickString(kalshi, p.kalshi_title);
|
|
899
|
+
const ticker = (kalshi && kalshi.market_ticker) || p.kalshi_ticker;
|
|
790
900
|
const sim = p.similarity != null ? ` · similarity ${formatPct(p.similarity, 0)}` : '';
|
|
791
|
-
lines.push(`${i + 1}. **Polymarket:** ${
|
|
792
|
-
|
|
793
|
-
(
|
|
794
|
-
|
|
901
|
+
lines.push(`${i + 1}. **Polymarket:** ${polyTitle}`);
|
|
902
|
+
if (kalshi) {
|
|
903
|
+
lines.push(` **Kalshi:** ${kalshiTitle ?? '(untitled)'}` +
|
|
904
|
+
(ticker ? ` · ticker=\`${ticker}\`` : '') +
|
|
905
|
+
sim);
|
|
906
|
+
}
|
|
907
|
+
else {
|
|
908
|
+
lines.push(` _(no Kalshi match)_${sim}`);
|
|
909
|
+
}
|
|
795
910
|
});
|
|
796
911
|
lines.push('', `_$0.005 paid via x402._`);
|
|
797
912
|
return { output: lines.join('\n') };
|
|
@@ -854,7 +969,7 @@ export const predictionMarketCapability = {
|
|
|
854
969
|
},
|
|
855
970
|
status: {
|
|
856
971
|
type: 'string',
|
|
857
|
-
description: '
|
|
972
|
+
description: 'Market status filter — Predexon accepts `open` or `closed` for Polymarket, Kalshi, and searchAll alike (default `open`). Synonyms like `active`/`archived` are normalized automatically.',
|
|
858
973
|
},
|
|
859
974
|
sort: {
|
|
860
975
|
type: 'string',
|
package/dist/tools/videogen.js
CHANGED
|
@@ -28,13 +28,6 @@ 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 token360 Asset UI (after H5 verification).
|
|
32
|
-
// 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
|
-
]);
|
|
38
31
|
const DEFAULT_MODEL = 'xai/grok-imagine-video';
|
|
39
32
|
const DEFAULT_DURATION = 8;
|
|
40
33
|
const PRICE_PER_SECOND_USD = 0.05;
|
|
@@ -51,33 +44,9 @@ function estimateVideoCostUsd(durationSeconds = DEFAULT_DURATION) {
|
|
|
51
44
|
function buildExecute(deps) {
|
|
52
45
|
return async function execute(input, ctx) {
|
|
53
46
|
const rawInput = input;
|
|
54
|
-
const { output_path, model, image_url, duration_seconds, contentId, aspect_ratio
|
|
47
|
+
const { output_path, model, image_url, duration_seconds, contentId, aspect_ratio } = rawInput;
|
|
55
48
|
if (!rawInput.prompt)
|
|
56
49
|
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). 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
|
-
}
|
|
81
50
|
// Resolve image_url before sending. The gateway requires a URL (http(s)
|
|
82
51
|
// or data: URI), but agents naturally pass a local file path —
|
|
83
52
|
// verified 2026-05-04 in a live session: agent passed
|
|
@@ -193,10 +162,6 @@ function buildExecute(deps) {
|
|
|
193
162
|
// value, the 400 body surfaces via 3.15.45 diagnostic so the agent
|
|
194
163
|
// can drop the param and retry.
|
|
195
164
|
...(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 } : {}),
|
|
200
165
|
});
|
|
201
166
|
const headers = {
|
|
202
167
|
'Content-Type': 'application/json',
|
|
@@ -537,14 +502,6 @@ export function createVideoGenCapability(deps = {}) {
|
|
|
537
502
|
'error body surfaces — drop the param and retry.',
|
|
538
503
|
},
|
|
539
504
|
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. Users get asset IDs from token360\'s Asset UI after H5 ' +
|
|
544
|
-
'verification. 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
|
-
},
|
|
548
505
|
},
|
|
549
506
|
required: ['prompt'],
|
|
550
507
|
},
|
package/package.json
CHANGED