unbrowse 2.9.1 → 2.10.2

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,137 @@
1
+ import { createHash } from "crypto";
2
+ import bs58 from "bs58";
3
+ import type { SkillManifest } from "../types/index.js";
4
+
5
+ type Contributor = NonNullable<SkillManifest["contributors"]>[number];
6
+
7
+ type CascadeSdk = {
8
+ createSplitsClient(args: {
9
+ rpc: unknown;
10
+ rpcSubscriptions: unknown;
11
+ signer: unknown;
12
+ }): {
13
+ ensureSplit(args: {
14
+ recipients: Array<{ address: string; share: number }>;
15
+ uniqueId?: unknown;
16
+ }): Promise<{
17
+ status: "created" | "updated" | "no_change" | "blocked" | "failed";
18
+ splitConfig?: string;
19
+ message?: string;
20
+ reason?: string;
21
+ }>;
22
+ };
23
+ labelToSeed(label: string): unknown;
24
+ };
25
+
26
+ type SolanaKit = {
27
+ createSolanaRpc(url: string): unknown;
28
+ createSolanaRpcSubscriptions(url: string): unknown;
29
+ createKeyPairSignerFromBytes(secretKey: Uint8Array): Promise<unknown>;
30
+ };
31
+
32
+ type CascadeDeps = {
33
+ loadSdk?: () => Promise<CascadeSdk>;
34
+ loadKit?: () => Promise<SolanaKit>;
35
+ env?: Record<string, string | undefined>;
36
+ };
37
+
38
+ export type CascadeProvisionResult = {
39
+ split_config?: string;
40
+ warning?: string;
41
+ source?: "env" | "sdk" | "existing";
42
+ };
43
+
44
+ function payableContributors(skill: Pick<SkillManifest, "contributors">): Contributor[] {
45
+ return (skill.contributors ?? []).filter((c): c is Contributor => !!c.wallet_address?.trim());
46
+ }
47
+
48
+ function cascadeLabel(skillId: string): string {
49
+ const digest = createHash("sha256").update(skillId).digest("hex");
50
+ return `ubr-${digest.slice(0, 23)}`;
51
+ }
52
+
53
+ function decodeSecretKey(raw: string): Uint8Array {
54
+ const trimmed = raw.trim();
55
+ if (!trimmed) throw new Error("empty signer secret");
56
+
57
+ if (trimmed.startsWith("[")) {
58
+ return Uint8Array.from(JSON.parse(trimmed) as number[]);
59
+ }
60
+
61
+ if (/^[1-9A-HJ-NP-Za-km-z]+$/.test(trimmed)) {
62
+ return Uint8Array.from(bs58.decode(trimmed));
63
+ }
64
+
65
+ return Uint8Array.from(Buffer.from(trimmed, "base64"));
66
+ }
67
+
68
+ function recipientsForSkill(
69
+ skill: Pick<SkillManifest, "contributors">,
70
+ platformWallet: string,
71
+ ): Array<{ address: string; share: number }> {
72
+ return [
73
+ { address: platformWallet, share: 10 },
74
+ ...payableContributors(skill).map((c) => ({
75
+ address: c.wallet_address!.trim(),
76
+ share: c.share,
77
+ })),
78
+ ];
79
+ }
80
+
81
+ export async function ensureCascadeSplitForSkill(
82
+ skill: Pick<SkillManifest, "skill_id" | "contributors" | "split_config">,
83
+ deps: CascadeDeps = {},
84
+ ): Promise<CascadeProvisionResult> {
85
+ const env = deps.env ?? process.env;
86
+ if (skill.split_config?.trim()) {
87
+ return { split_config: skill.split_config.trim(), source: "existing" };
88
+ }
89
+
90
+ const contributors = payableContributors(skill);
91
+ if (contributors.length <= 1) return {};
92
+
93
+ const explicitSplitConfig = env.UNBROWSE_CASCADE_SPLIT_ADDRESS?.trim()
94
+ || env.UNBROWSE_CASCADE_SPLIT_CONFIG?.trim();
95
+ if (explicitSplitConfig) {
96
+ return { split_config: explicitSplitConfig, source: "env" };
97
+ }
98
+
99
+ const platformWallet = env.UNBROWSE_CASCADE_PLATFORM_WALLET?.trim()
100
+ || env.PAYMENT_RECIPIENT?.trim();
101
+ const secretKey = env.UNBROWSE_CASCADE_SIGNER_SECRET_KEY?.trim();
102
+ const rpcUrl = env.UNBROWSE_CASCADE_RPC_URL?.trim();
103
+ const rpcWsUrl = env.UNBROWSE_CASCADE_RPC_WS_URL?.trim();
104
+
105
+ if (!platformWallet || !secretKey || !rpcUrl || !rpcWsUrl) {
106
+ return {
107
+ warning: "cascade_split_not_configured",
108
+ };
109
+ }
110
+
111
+ const loadSdk = deps.loadSdk ?? (async () => await import("@cascade-fyi/splits-sdk") as unknown as CascadeSdk);
112
+ const loadKit = deps.loadKit ?? (async () => await import("@solana/kit") as unknown as SolanaKit);
113
+ const [sdk, kit] = await Promise.all([loadSdk(), loadKit()]);
114
+
115
+ const signer = await kit.createKeyPairSignerFromBytes(decodeSecretKey(secretKey));
116
+ const splits = sdk.createSplitsClient({
117
+ rpc: kit.createSolanaRpc(rpcUrl),
118
+ rpcSubscriptions: kit.createSolanaRpcSubscriptions(rpcWsUrl),
119
+ signer,
120
+ });
121
+
122
+ const result = await splits.ensureSplit({
123
+ recipients: recipientsForSkill(skill, platformWallet),
124
+ uniqueId: sdk.labelToSeed(cascadeLabel(skill.skill_id)),
125
+ });
126
+
127
+ if (result.status === "created" || result.status === "updated" || result.status === "no_change") {
128
+ return {
129
+ split_config: result.splitConfig,
130
+ source: "sdk",
131
+ };
132
+ }
133
+
134
+ return {
135
+ warning: result.message || `cascade_split_${result.status}`,
136
+ };
137
+ }
@@ -328,6 +328,8 @@ export interface ExecutionOptions {
328
328
  force_capture?: boolean;
329
329
  /** Request/client namespace for isolating local server state across concurrent CLI users */
330
330
  client_scope?: string;
331
+ /** Set only when the caller has already completed payment verification for a paid run */
332
+ payment_verified?: boolean;
331
333
  }
332
334
 
333
335
  export interface ValidationResult {
@@ -355,6 +357,22 @@ export interface OrchestrationTiming {
355
357
  time_saved_pct: number;
356
358
  /** Percentage token saved vs estimated full-page browsing cost */
357
359
  tokens_saved_pct: number;
360
+ /** Real capture baseline in ms when known */
361
+ baseline_total_ms?: number;
362
+ /** Actual runtime latency in ms for this resolve */
363
+ actual_total_ms?: number;
364
+ /** Real time saved in ms when baseline is known */
365
+ time_saved_ms?: number;
366
+ /** Real baseline cost in micro-cents when known */
367
+ baseline_cost_uc?: number;
368
+ /** Real amount charged for this run in micro-cents */
369
+ actual_cost_uc?: number;
370
+ /** Real cost saved in micro-cents when baseline is known */
371
+ cost_saved_uc?: number;
372
+ /** Tier 3 search fee charged during this resolve */
373
+ paid_search_uc?: number;
374
+ /** Paid execution fee charged during this resolve */
375
+ paid_execution_uc?: number;
358
376
  /** Code version hash + git SHA — tracks which code produced this timing */
359
377
  trace_version?: string;
360
378
  }