unbrowse 8.0.0 → 8.1.1
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/bin/unbrowse-wrapper.mjs +8 -40
- package/bin/unbrowse.js +54 -41
- package/dist-sdk/adapters/browser-use.d.ts +28 -0
- package/dist-sdk/adapters/browser-use.js +28 -0
- package/dist-sdk/adapters/exa.d.ts +49 -0
- package/dist-sdk/adapters/exa.js +50 -0
- package/dist-sdk/adapters/index.d.ts +16 -0
- package/dist-sdk/adapters/index.js +16 -0
- package/dist-sdk/adapters/tavily.d.ts +36 -0
- package/dist-sdk/adapters/tavily.js +39 -0
- package/dist-sdk/hole.d.ts +119 -0
- package/dist-sdk/hole.js +177 -0
- package/dist-sdk/onboard.d.ts +31 -0
- package/dist-sdk/onboard.js +81 -0
- package/package.json +5 -3
- package/runtime/cli.js +170265 -0
- package/scripts/postinstall.mjs +37 -140
- package/vendor/kuri/darwin-arm64/libkuri_ffi.dylib +0 -0
- package/vendor/kuri/darwin-x64/libkuri_ffi.dylib +0 -0
- package/vendor/kuri/linux-arm64/libkuri_ffi.so +0 -0
- package/vendor/kuri/linux-x64/kuri +0 -0
- package/vendor/kuri/linux-x64/libkuri_ffi.so +0 -0
- package/vendor/kuri/manifest.json +7 -7
- package/vendor/kuri/win-x64/kuri.exe +0 -0
package/bin/unbrowse-wrapper.mjs
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Thin wrapper —
|
|
5
|
-
*
|
|
4
|
+
* Thin wrapper — runs an explicitly-provided compiled binary if present (the
|
|
5
|
+
* UNBROWSE_INSTALL_BINARY_PATH opt-in, used by CI smoke tests), else the package's
|
|
6
|
+
* readable, unsigned runtime via the launcher. There is NO auto-download fallback —
|
|
7
|
+
* the readable runtime is the default; the runtime IS the client.
|
|
6
8
|
*/
|
|
7
9
|
|
|
8
10
|
import { existsSync, readFileSync } from "node:fs";
|
|
9
|
-
import { createRequire } from "node:module";
|
|
10
11
|
import { join, dirname } from "node:path";
|
|
11
12
|
import { fileURLToPath } from "node:url";
|
|
12
13
|
import { spawn } from "node:child_process";
|
|
@@ -14,21 +15,8 @@ import { unbrowseBinaryName } from "../scripts/release-assets.mjs";
|
|
|
14
15
|
|
|
15
16
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
17
|
const packageRoot = join(__dirname, "..");
|
|
17
|
-
// Windows ships `unbrowse.exe`; postinstall installs it under that name, so the
|
|
18
|
-
// wrapper must look for the same name (a plain `unbrowse` never exists on win).
|
|
19
18
|
const binaryPath = join(__dirname, unbrowseBinaryName(process.platform));
|
|
20
19
|
const launcherPath = join(__dirname, "unbrowse.js");
|
|
21
|
-
const require = createRequire(import.meta.url);
|
|
22
|
-
|
|
23
|
-
const REQUIRED_FALLBACK_PACKAGES = [
|
|
24
|
-
"tsx",
|
|
25
|
-
"bs58",
|
|
26
|
-
"@solana/kit",
|
|
27
|
-
];
|
|
28
|
-
|
|
29
|
-
const KNOWN_BAD_FALLBACK_VERSIONS = new Set([
|
|
30
|
-
"2.10.2",
|
|
31
|
-
]);
|
|
32
20
|
|
|
33
21
|
function readInstalledVersion() {
|
|
34
22
|
try {
|
|
@@ -54,18 +42,6 @@ function failInstall(reason, exitCode = 1) {
|
|
|
54
42
|
process.exit(exitCode);
|
|
55
43
|
}
|
|
56
44
|
|
|
57
|
-
function missingFallbackPackages() {
|
|
58
|
-
const missing = [];
|
|
59
|
-
for (const specifier of REQUIRED_FALLBACK_PACKAGES) {
|
|
60
|
-
try {
|
|
61
|
-
require.resolve(specifier);
|
|
62
|
-
} catch {
|
|
63
|
-
missing.push(specifier);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return missing;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
45
|
function spawnEntrypoint(command, args) {
|
|
70
46
|
const child = spawn(command, args, {
|
|
71
47
|
stdio: "inherit",
|
|
@@ -91,19 +67,11 @@ if (process.argv.includes("--version") || process.argv.includes("-v")) {
|
|
|
91
67
|
}
|
|
92
68
|
|
|
93
69
|
if (existsSync(binaryPath)) {
|
|
70
|
+
// an explicitly-injected binary (UNBROWSE_INSTALL_BINARY_PATH) — not a fallback
|
|
94
71
|
spawnEntrypoint(binaryPath, process.argv.slice(2));
|
|
95
72
|
} else {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
`Installed package version ${installedVersion} is known-bad in source fallback mode and shipped an incomplete runtime dependency set.`,
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const missing = missingFallbackPackages();
|
|
104
|
-
if (missing.length > 0) {
|
|
105
|
-
failInstall(`Fallback runtime is missing required packages: ${missing.join(", ")}.`);
|
|
106
|
-
}
|
|
107
|
-
|
|
73
|
+
// the default: the readable, unsigned runtime via the launcher. The source IS the
|
|
74
|
+
// runtime, so the client is auditable on disk — security lives in the wallet-sealed
|
|
75
|
+
// crypto, not in hiding the client. The hole fills any internet gap.
|
|
108
76
|
spawnEntrypoint(process.execPath, [launcherPath, ...process.argv.slice(2)]);
|
|
109
77
|
}
|
package/bin/unbrowse.js
CHANGED
|
@@ -1,50 +1,63 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Auditable runtime launcher.
|
|
5
|
+
*
|
|
6
|
+
* The package ships a READABLE, UNSIGNED runtime at `runtime/cli.js` (an
|
|
7
|
+
* unminified Bun bundle of the source) and runs it here. The source IS the
|
|
8
|
+
* runtime, so the client — including the zk/crypto verification path — is fully
|
|
9
|
+
* auditable on disk. There is nothing to hide: every secret is wallet-sealed
|
|
10
|
+
* (AES-GCM under a key only the holder derives), so reverse-engineering the
|
|
11
|
+
* client yields nothing. Security lives in the wallet, not in obfuscation.
|
|
12
|
+
*
|
|
13
|
+
* The runtime requires Bun. If a compiled binary was downloaded post-install,
|
|
14
|
+
* the wrapper runs that instead and never reaches this launcher.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { existsSync } from "node:fs";
|
|
15
18
|
import { dirname, join } from "node:path";
|
|
16
19
|
import { fileURLToPath } from "node:url";
|
|
20
|
+
import { spawnSync } from "node:child_process";
|
|
21
|
+
|
|
22
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
const runtime = join(here, "..", "runtime", "cli.js");
|
|
17
24
|
|
|
18
|
-
|
|
25
|
+
function fail(msg, code = 1) {
|
|
26
|
+
process.stderr.write(`[unbrowse] ${msg}\n`);
|
|
27
|
+
process.exit(code);
|
|
28
|
+
}
|
|
19
29
|
|
|
20
|
-
function
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
30
|
+
function findBun() {
|
|
31
|
+
const candidates = [
|
|
32
|
+
process.env.UNBROWSE_BUN_BIN,
|
|
33
|
+
"bun",
|
|
34
|
+
join(process.env.HOME || "", ".bun", "bin", "bun"),
|
|
35
|
+
].filter(Boolean);
|
|
36
|
+
for (const candidate of candidates) {
|
|
37
|
+
try {
|
|
38
|
+
const probe = spawnSync(candidate, ["--version"], { stdio: "ignore" });
|
|
39
|
+
if (probe.status === 0) return candidate;
|
|
40
|
+
} catch {
|
|
41
|
+
/* try the next candidate */
|
|
42
|
+
}
|
|
26
43
|
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!existsSync(runtime)) {
|
|
48
|
+
fail("readable runtime missing (runtime/cli.js). Reinstall: npm install -g unbrowse@latest");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const bun = findBun();
|
|
52
|
+
if (!bun) {
|
|
53
|
+
fail("this build runs on Bun. Install it from https://bun.sh, then re-run (or set UNBROWSE_BUN_BIN).");
|
|
27
54
|
}
|
|
28
55
|
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
process.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"",
|
|
38
|
-
"[unbrowse] The npm package ships only the native binary downloaded on",
|
|
39
|
-
"[unbrowse] postinstall. Possible causes:",
|
|
40
|
-
"[unbrowse] - postinstall was skipped (CI / UNBROWSE_SKIP_BINARY_DOWNLOAD)",
|
|
41
|
-
"[unbrowse] - download failed (check stderr from `npm install`)",
|
|
42
|
-
"[unbrowse] - platform is not in the prebuilt set",
|
|
43
|
-
"",
|
|
44
|
-
"[unbrowse] Repair:",
|
|
45
|
-
"[unbrowse] node packages/skill/scripts/postinstall.mjs # retry download",
|
|
46
|
-
"[unbrowse] npm uninstall -g unbrowse && npm install -g unbrowse@latest",
|
|
47
|
-
"[unbrowse] build from source: https://github.com/unbrowse-ai/unbrowse",
|
|
48
|
-
].join("\n") + "\n",
|
|
49
|
-
);
|
|
50
|
-
process.exit(1);
|
|
56
|
+
const child = spawnSync(bun, [runtime, ...process.argv.slice(2)], {
|
|
57
|
+
stdio: "inherit",
|
|
58
|
+
cwd: process.cwd(),
|
|
59
|
+
env: process.env,
|
|
60
|
+
});
|
|
61
|
+
if (child.error) fail(`failed to launch runtime: ${child.error.message}`);
|
|
62
|
+
if (child.signal) process.kill(process.pid, child.signal);
|
|
63
|
+
process.exit(child.status ?? 1);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/sdk/adapters/browser-use.ts — drop-in shape for the `browser-use` Agent.
|
|
3
|
+
*
|
|
4
|
+
* Same construction (`new Agent({ task, llm })`) and `.run()` — but the task is filled
|
|
5
|
+
* through the wallet-sealed unbrowse hole (resolve → execute → capture) instead of a
|
|
6
|
+
* driven headful browser. `llm` is accepted for signature compatibility; the hole
|
|
7
|
+
* decides when a real browser is actually needed (browser-open is the fallback, not the
|
|
8
|
+
* substrate).
|
|
9
|
+
*/
|
|
10
|
+
import { type HoleItem, type HoleOptions } from "../hole.js";
|
|
11
|
+
export interface AgentOptions extends HoleOptions {
|
|
12
|
+
task: string;
|
|
13
|
+
llm?: unknown;
|
|
14
|
+
maxSteps?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface AgentResult {
|
|
17
|
+
task: string;
|
|
18
|
+
done: boolean;
|
|
19
|
+
result: string;
|
|
20
|
+
items: HoleItem[];
|
|
21
|
+
}
|
|
22
|
+
export declare class Agent {
|
|
23
|
+
private readonly hole;
|
|
24
|
+
private readonly task;
|
|
25
|
+
constructor(options: AgentOptions);
|
|
26
|
+
run(): Promise<AgentResult>;
|
|
27
|
+
}
|
|
28
|
+
export default Agent;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/sdk/adapters/browser-use.ts — drop-in shape for the `browser-use` Agent.
|
|
3
|
+
*
|
|
4
|
+
* Same construction (`new Agent({ task, llm })`) and `.run()` — but the task is filled
|
|
5
|
+
* through the wallet-sealed unbrowse hole (resolve → execute → capture) instead of a
|
|
6
|
+
* driven headful browser. `llm` is accepted for signature compatibility; the hole
|
|
7
|
+
* decides when a real browser is actually needed (browser-open is the fallback, not the
|
|
8
|
+
* substrate).
|
|
9
|
+
*/
|
|
10
|
+
import { createHole } from "../hole.js";
|
|
11
|
+
export class Agent {
|
|
12
|
+
hole;
|
|
13
|
+
task;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.task = options.task;
|
|
16
|
+
this.hole = createHole(options);
|
|
17
|
+
}
|
|
18
|
+
async run() {
|
|
19
|
+
const r = await this.hole.fill({ intent: this.task });
|
|
20
|
+
return {
|
|
21
|
+
task: this.task,
|
|
22
|
+
done: r.ok,
|
|
23
|
+
result: r.answer ?? r.items[0]?.text ?? "",
|
|
24
|
+
items: r.items,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export default Agent;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/sdk/adapters/exa.ts — drop-in replacement for the `exa-js` client.
|
|
3
|
+
*
|
|
4
|
+
* Same construction (`new Exa(apiKey)`) and the same method shapes (`search`,
|
|
5
|
+
* `searchAndContents`, `getContents`, `answer`) returning exa-shaped results — but every
|
|
6
|
+
* call routes through the wallet-sealed unbrowse hole instead of Exa's API. Swap the
|
|
7
|
+
* import, keep your code. Inject a `transport`/`wallet` (HoleOptions) for tests or to
|
|
8
|
+
* bind the hole to a wallet.
|
|
9
|
+
*/
|
|
10
|
+
import { type HoleOptions } from "../hole.js";
|
|
11
|
+
export interface ExaSearchOptions {
|
|
12
|
+
numResults?: number;
|
|
13
|
+
type?: string;
|
|
14
|
+
includeDomains?: string[];
|
|
15
|
+
startPublishedDate?: string;
|
|
16
|
+
contents?: {
|
|
17
|
+
text?: boolean;
|
|
18
|
+
highlights?: boolean;
|
|
19
|
+
summary?: boolean;
|
|
20
|
+
};
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
export interface ExaResult {
|
|
24
|
+
title: string | null;
|
|
25
|
+
url: string;
|
|
26
|
+
publishedDate?: string;
|
|
27
|
+
score?: number;
|
|
28
|
+
text?: string;
|
|
29
|
+
highlights?: string[];
|
|
30
|
+
summary?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface ExaSearchResponse {
|
|
33
|
+
results: ExaResult[];
|
|
34
|
+
autopromptString?: string;
|
|
35
|
+
}
|
|
36
|
+
export declare class Exa {
|
|
37
|
+
private readonly hole;
|
|
38
|
+
constructor(_apiKey?: string, opts?: HoleOptions);
|
|
39
|
+
search(query: string, options?: ExaSearchOptions): Promise<ExaSearchResponse>;
|
|
40
|
+
searchAndContents(query: string, options?: ExaSearchOptions): Promise<ExaSearchResponse>;
|
|
41
|
+
getContents(urls: string[], _options?: {
|
|
42
|
+
text?: boolean;
|
|
43
|
+
highlights?: boolean;
|
|
44
|
+
}): Promise<ExaSearchResponse>;
|
|
45
|
+
answer(question: string): Promise<{
|
|
46
|
+
answer: string;
|
|
47
|
+
}>;
|
|
48
|
+
}
|
|
49
|
+
export default Exa;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/sdk/adapters/exa.ts — drop-in replacement for the `exa-js` client.
|
|
3
|
+
*
|
|
4
|
+
* Same construction (`new Exa(apiKey)`) and the same method shapes (`search`,
|
|
5
|
+
* `searchAndContents`, `getContents`, `answer`) returning exa-shaped results — but every
|
|
6
|
+
* call routes through the wallet-sealed unbrowse hole instead of Exa's API. Swap the
|
|
7
|
+
* import, keep your code. Inject a `transport`/`wallet` (HoleOptions) for tests or to
|
|
8
|
+
* bind the hole to a wallet.
|
|
9
|
+
*/
|
|
10
|
+
import { createHole } from "../hole.js";
|
|
11
|
+
function toExa(it) {
|
|
12
|
+
return {
|
|
13
|
+
title: it.title ?? null,
|
|
14
|
+
url: it.url ?? "",
|
|
15
|
+
publishedDate: it.publishedDate,
|
|
16
|
+
score: it.score,
|
|
17
|
+
text: typeof it.text === "string" ? it.text : undefined,
|
|
18
|
+
highlights: Array.isArray(it.highlights) ? it.highlights : undefined,
|
|
19
|
+
summary: typeof it.summary === "string" ? it.summary : undefined,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export class Exa {
|
|
23
|
+
hole;
|
|
24
|
+
constructor(_apiKey, opts = {}) {
|
|
25
|
+
this.hole = createHole(opts);
|
|
26
|
+
}
|
|
27
|
+
async search(query, options = {}) {
|
|
28
|
+
const r = await this.hole.fill({ intent: query, params: options });
|
|
29
|
+
const n = options.numResults ?? r.items.length;
|
|
30
|
+
return { results: r.items.slice(0, n).map(toExa) };
|
|
31
|
+
}
|
|
32
|
+
async searchAndContents(query, options = {}) {
|
|
33
|
+
return this.search(query, { ...options, contents: { text: true, ...(options.contents ?? {}) } });
|
|
34
|
+
}
|
|
35
|
+
async getContents(urls, _options = {}) {
|
|
36
|
+
const results = [];
|
|
37
|
+
for (const url of urls) {
|
|
38
|
+
const r = await this.hole.fill({ intent: `contents of ${url}`, url });
|
|
39
|
+
const first = r.items[0];
|
|
40
|
+
if (first)
|
|
41
|
+
results.push(toExa({ ...first, url }));
|
|
42
|
+
}
|
|
43
|
+
return { results };
|
|
44
|
+
}
|
|
45
|
+
async answer(question) {
|
|
46
|
+
const r = await this.hole.fill({ intent: question });
|
|
47
|
+
return { answer: r.answer ?? r.items[0]?.text ?? "" };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export default Exa;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/sdk/adapters/index.ts — drop-in client adapters over the unbrowse hole.
|
|
3
|
+
*
|
|
4
|
+
* Each adapter mirrors a popular client's construction + method shapes, so swapping the
|
|
5
|
+
* import is the only change needed:
|
|
6
|
+
*
|
|
7
|
+
* import Exa from "@unbrowse/sdk/adapters/exa"; // was: import Exa from "exa-js"
|
|
8
|
+
* import { tavily } from "@unbrowse/sdk/adapters/tavily"; // was: from "@tavily/core"
|
|
9
|
+
* import { Agent } from "@unbrowse/sdk/adapters/browser-use";
|
|
10
|
+
*
|
|
11
|
+
* All of them wrap the same wallet-sealed streaming hole (`../hole.ts`).
|
|
12
|
+
*/
|
|
13
|
+
export { Exa, type ExaResult, type ExaSearchResponse, type ExaSearchOptions } from "./exa.js";
|
|
14
|
+
export { tavily, type TavilyClient, type TavilyResult, type TavilySearchResponse } from "./tavily.js";
|
|
15
|
+
export { Agent, type AgentOptions, type AgentResult } from "./browser-use.js";
|
|
16
|
+
export { createHole, Hole, type HoleRequest, type HoleResult, type HoleItem, type HoleOptions, type HoleTransport, type WalletSeal, } from "../hole.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/sdk/adapters/index.ts — drop-in client adapters over the unbrowse hole.
|
|
3
|
+
*
|
|
4
|
+
* Each adapter mirrors a popular client's construction + method shapes, so swapping the
|
|
5
|
+
* import is the only change needed:
|
|
6
|
+
*
|
|
7
|
+
* import Exa from "@unbrowse/sdk/adapters/exa"; // was: import Exa from "exa-js"
|
|
8
|
+
* import { tavily } from "@unbrowse/sdk/adapters/tavily"; // was: from "@tavily/core"
|
|
9
|
+
* import { Agent } from "@unbrowse/sdk/adapters/browser-use";
|
|
10
|
+
*
|
|
11
|
+
* All of them wrap the same wallet-sealed streaming hole (`../hole.ts`).
|
|
12
|
+
*/
|
|
13
|
+
export { Exa } from "./exa.js";
|
|
14
|
+
export { tavily } from "./tavily.js";
|
|
15
|
+
export { Agent } from "./browser-use.js";
|
|
16
|
+
export { createHole, Hole, } from "../hole.js";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/sdk/adapters/tavily.ts — drop-in replacement for the `@tavily/core` client.
|
|
3
|
+
*
|
|
4
|
+
* Same construction (`tavily({ apiKey })`) and the same methods (`search`, `extract`)
|
|
5
|
+
* returning tavily-shaped responses — routed through the wallet-sealed unbrowse hole.
|
|
6
|
+
*/
|
|
7
|
+
import { type HoleOptions } from "../hole.js";
|
|
8
|
+
export interface TavilyResult {
|
|
9
|
+
title: string;
|
|
10
|
+
url: string;
|
|
11
|
+
content: string;
|
|
12
|
+
score: number;
|
|
13
|
+
rawContent?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface TavilySearchResponse {
|
|
16
|
+
query: string;
|
|
17
|
+
answer?: string;
|
|
18
|
+
results: TavilyResult[];
|
|
19
|
+
responseTime?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface TavilyExtractResponse {
|
|
22
|
+
results: Array<{
|
|
23
|
+
url: string;
|
|
24
|
+
rawContent: string;
|
|
25
|
+
}>;
|
|
26
|
+
failedResults: string[];
|
|
27
|
+
}
|
|
28
|
+
export interface TavilyOptions extends HoleOptions {
|
|
29
|
+
apiKey?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface TavilyClient {
|
|
32
|
+
search(query: string, options?: Record<string, unknown>): Promise<TavilySearchResponse>;
|
|
33
|
+
extract(urls: string[]): Promise<TavilyExtractResponse>;
|
|
34
|
+
}
|
|
35
|
+
export declare function tavily(options?: TavilyOptions): TavilyClient;
|
|
36
|
+
export default tavily;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/sdk/adapters/tavily.ts — drop-in replacement for the `@tavily/core` client.
|
|
3
|
+
*
|
|
4
|
+
* Same construction (`tavily({ apiKey })`) and the same methods (`search`, `extract`)
|
|
5
|
+
* returning tavily-shaped responses — routed through the wallet-sealed unbrowse hole.
|
|
6
|
+
*/
|
|
7
|
+
import { createHole } from "../hole.js";
|
|
8
|
+
function toTavily(it) {
|
|
9
|
+
return {
|
|
10
|
+
title: it.title ?? "",
|
|
11
|
+
url: it.url ?? "",
|
|
12
|
+
content: typeof it.text === "string" ? it.text : "",
|
|
13
|
+
score: typeof it.score === "number" ? it.score : 0,
|
|
14
|
+
rawContent: typeof it.rawContent === "string" ? it.rawContent : undefined,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function tavily(options = {}) {
|
|
18
|
+
const hole = createHole(options);
|
|
19
|
+
return {
|
|
20
|
+
async search(query, opts = {}) {
|
|
21
|
+
const r = await hole.fill({ intent: query, params: opts });
|
|
22
|
+
return { query, answer: r.answer, results: r.items.map(toTavily) };
|
|
23
|
+
},
|
|
24
|
+
async extract(urls) {
|
|
25
|
+
const results = [];
|
|
26
|
+
const failedResults = [];
|
|
27
|
+
for (const url of urls) {
|
|
28
|
+
const r = await hole.fill({ intent: `extract ${url}`, url });
|
|
29
|
+
const text = r.items[0]?.text;
|
|
30
|
+
if (typeof text === "string" && text)
|
|
31
|
+
results.push({ url, rawContent: text });
|
|
32
|
+
else
|
|
33
|
+
failedResults.push(url);
|
|
34
|
+
}
|
|
35
|
+
return { results, failedResults };
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export default tavily;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { UnbrowseClientOptions } from "./types.js";
|
|
2
|
+
/** What the caller hands the hole: an intent, optionally scoped to a URL. */
|
|
3
|
+
export interface HoleRequest {
|
|
4
|
+
intent: string;
|
|
5
|
+
url?: string;
|
|
6
|
+
params?: Record<string, unknown>;
|
|
7
|
+
/** Perform an ACTION (execute / fill / submit) rather than just read/search. */
|
|
8
|
+
act?: boolean;
|
|
9
|
+
/** Override the hole's auto-index default for this one request. */
|
|
10
|
+
autoIndex?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** A reusable route the hole learned while filling a gap (what gets indexed). */
|
|
13
|
+
export interface HoleSkill {
|
|
14
|
+
domain?: string;
|
|
15
|
+
name?: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
}
|
|
18
|
+
/** Payload handed to the index hook when a captured route is auto-indexed. */
|
|
19
|
+
export interface IndexInfo {
|
|
20
|
+
intent: string;
|
|
21
|
+
skill: HoleSkill;
|
|
22
|
+
items: HoleItem[];
|
|
23
|
+
}
|
|
24
|
+
/** One normalized result item — the lingua franca every adapter reshapes from. */
|
|
25
|
+
export interface HoleItem {
|
|
26
|
+
title?: string;
|
|
27
|
+
url?: string;
|
|
28
|
+
text?: string;
|
|
29
|
+
score?: number;
|
|
30
|
+
publishedDate?: string;
|
|
31
|
+
[key: string]: unknown;
|
|
32
|
+
}
|
|
33
|
+
/** A wallet attestation attached when the hole is sealed (the zk'd hole). */
|
|
34
|
+
export interface HoleSeal {
|
|
35
|
+
walletPubkey: string;
|
|
36
|
+
signature: string;
|
|
37
|
+
}
|
|
38
|
+
export interface HoleResult {
|
|
39
|
+
ok: boolean;
|
|
40
|
+
intent: string;
|
|
41
|
+
items: HoleItem[];
|
|
42
|
+
answer?: string;
|
|
43
|
+
source?: string;
|
|
44
|
+
raw?: unknown;
|
|
45
|
+
seal?: HoleSeal;
|
|
46
|
+
/** True when filling required a fresh capture (a new route was learned). */
|
|
47
|
+
captured?: boolean;
|
|
48
|
+
/** The learned route, when captured — what the auto-index hook receives. */
|
|
49
|
+
skill?: HoleSkill;
|
|
50
|
+
/** True when this fill auto-indexed the captured route. */
|
|
51
|
+
indexed?: boolean;
|
|
52
|
+
}
|
|
53
|
+
/** The pluggable backend: turn a request into normalized items. */
|
|
54
|
+
export type HoleTransport = (req: HoleRequest) => Promise<Omit<HoleResult, "seal">>;
|
|
55
|
+
/** Minimal wallet signer the hole seals to (any Ed25519-style signer fits). */
|
|
56
|
+
export interface WalletSeal {
|
|
57
|
+
sign(message: Uint8Array): Promise<{
|
|
58
|
+
signature: Uint8Array;
|
|
59
|
+
walletPubkey: Uint8Array;
|
|
60
|
+
}>;
|
|
61
|
+
}
|
|
62
|
+
export interface HoleOptions {
|
|
63
|
+
transport?: HoleTransport;
|
|
64
|
+
wallet?: WalletSeal;
|
|
65
|
+
client?: UnbrowseClientOptions;
|
|
66
|
+
/** Auto-index captured routes after a fill (default true). */
|
|
67
|
+
autoIndex?: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Generate a name + description for a captured route (default true). We generate
|
|
70
|
+
* descriptions for the user, not the other way round — a zero-cost deterministic
|
|
71
|
+
* baseline always, enriched by the `generate` hook (a cheap model) when provided.
|
|
72
|
+
* Set false only if you truly want routes indexed with no description.
|
|
73
|
+
*/
|
|
74
|
+
describe?: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Where a captured route is sent to be indexed — the discover → publish loop.
|
|
77
|
+
* Wire this to the CLI's `queueBackgroundIndex`; left unset, fills still work but
|
|
78
|
+
* nothing is indexed (result.indexed = false).
|
|
79
|
+
*/
|
|
80
|
+
index?: (info: IndexInfo) => Promise<void> | void;
|
|
81
|
+
/**
|
|
82
|
+
* Client-side LLM used to "index it nicely" — name + describe a captured route.
|
|
83
|
+
* Keeps generation ON THE CLIENT (the agent's own model); no server round-trip.
|
|
84
|
+
* Given the captured route, return a short "<name> — <description>".
|
|
85
|
+
*/
|
|
86
|
+
generate?: (prompt: string) => Promise<string> | string;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Zero-cost deterministic name + description for a captured route. The always-on baseline
|
|
90
|
+
* so a learned route is never left undescribed and the USER never has to write one; the
|
|
91
|
+
* optional `generate` hook (a cheap model) enriches this when present.
|
|
92
|
+
*/
|
|
93
|
+
export declare function defaultDescribe(intent: string, skill: HoleSkill, items: HoleItem[]): {
|
|
94
|
+
name: string;
|
|
95
|
+
description: string;
|
|
96
|
+
};
|
|
97
|
+
/** Stable bytes to sign — key order fixed so client and verifier agree. */
|
|
98
|
+
export declare function canonicalRequest(req: HoleRequest): Uint8Array;
|
|
99
|
+
/** The hole. One method that matters: `fill`. Stream with `stream`. */
|
|
100
|
+
export declare class Hole {
|
|
101
|
+
private readonly transport;
|
|
102
|
+
private readonly wallet?;
|
|
103
|
+
private readonly autoIndex;
|
|
104
|
+
private readonly describe;
|
|
105
|
+
private readonly index?;
|
|
106
|
+
private readonly generate?;
|
|
107
|
+
constructor(opts?: HoleOptions);
|
|
108
|
+
/**
|
|
109
|
+
* Fill the gap — READ (search) or ACT (execute/fill/submit, when `req.act`). When the
|
|
110
|
+
* hole is wallet-bound the result carries a seal. When the fill required a fresh
|
|
111
|
+
* capture, the route is auto-indexed (named/described by the client-side `generate`
|
|
112
|
+
* hook, then handed to `index`) — the discover → publish loop, on the client.
|
|
113
|
+
*/
|
|
114
|
+
fill(req: HoleRequest): Promise<HoleResult>;
|
|
115
|
+
/** Stream the filled items one at a time (the "streaming shit into it" surface). */
|
|
116
|
+
stream(req: HoleRequest): AsyncIterable<HoleItem>;
|
|
117
|
+
}
|
|
118
|
+
/** Construct a hole. With `wallet`, it is the zk'd, wallet-protected hole. */
|
|
119
|
+
export declare function createHole(opts?: HoleOptions): Hole;
|