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.
- package/dist/cli.js +275 -34
- package/package.json +1 -1
- package/runtime-src/analytics-session.ts +33 -0
- package/runtime-src/api/routes.ts +59 -8
- package/runtime-src/auth/index.ts +44 -6
- package/runtime-src/client/index.ts +143 -9
- package/runtime-src/execution/index.ts +2 -1
- package/runtime-src/orchestrator/index.ts +15 -1
- package/runtime-src/payments/cascade.ts +137 -0
- package/runtime-src/types/skill.ts +18 -0
|
@@ -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
|
}
|