@humain/terminal 0.0.3-beta.2 → 0.0.4-beta.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/CHANGELOG.md +16 -0
- package/dist/bun/cli.d.ts.map +1 -1
- package/dist/bun/cli.js +0 -1
- package/dist/bun/cli.js.map +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +7 -1
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +4 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +46 -22
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/tools/index.d.ts +2 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +9 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/subagent.d.ts +82 -0
- package/dist/core/tools/subagent.d.ts.map +1 -0
- package/dist/core/tools/subagent.js +980 -0
- package/dist/core/tools/subagent.js.map +1 -0
- package/dist/humain/bedrock-provider/vendor/index.bundle.d.mts +3 -0
- package/dist/humain/bedrock-provider/vendor/index.bundle.mjs +1509 -0
- package/dist/humain/index.d.ts.map +1 -1
- package/dist/humain/index.js +3 -0
- package/dist/humain/index.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/CHANGELOG.md +36 -0
- package/dist/humain/mcp-adapter/vendor/README.md +47 -5
- package/dist/humain/mcp-adapter/vendor/cli.js +163 -1
- package/dist/humain/mcp-adapter/vendor/commands.ts +128 -22
- package/dist/humain/mcp-adapter/vendor/config.ts +25 -7
- package/dist/humain/mcp-adapter/vendor/direct-tools.ts +5 -4
- package/dist/humain/mcp-adapter/vendor/index.bundle.mjs +1663 -458
- package/dist/humain/mcp-adapter/vendor/index.ts +219 -32
- package/dist/humain/mcp-adapter/vendor/init.ts +36 -4
- package/dist/humain/mcp-adapter/vendor/lifecycle.ts +315 -24
- package/dist/humain/mcp-adapter/vendor/mcp-auth-flow.ts +39 -6
- package/dist/humain/mcp-adapter/vendor/mcp-bearer-store.ts +387 -0
- package/dist/humain/mcp-adapter/vendor/mcp-callback-server.ts +27 -3
- package/dist/humain/mcp-adapter/vendor/mcp-code.ts +4 -0
- package/dist/humain/mcp-adapter/vendor/mcp-panel.ts +42 -13
- package/dist/humain/mcp-adapter/vendor/metadata-cache.ts +32 -21
- package/dist/humain/mcp-adapter/vendor/package-mcp-loader.ts +168 -0
- package/dist/humain/mcp-adapter/vendor/package.json +179 -177
- package/dist/humain/mcp-adapter/vendor/proxy-modes.ts +5 -4
- package/dist/humain/mcp-adapter/vendor/request-headers-command.ts +31 -37
- package/dist/humain/mcp-adapter/vendor/search-ranking.ts +92 -22
- package/dist/humain/mcp-adapter/vendor/server-manager.ts +140 -2
- package/dist/humain/mcp-adapter/vendor/session-recovery.ts +7 -0
- package/dist/humain/mcp-adapter/vendor/tool-approval.ts +15 -2
- package/dist/humain/mcp-adapter/vendor/tool-result-renderer.ts +44 -4
- package/dist/humain/mcp-adapter/vendor/types.ts +2 -0
- package/dist/humain/mcp-adapter/vendor/ui-server.ts +18 -16
- package/dist/humain/mcp-adapter/vendor/utils.ts +70 -0
- package/dist/humain/web-access/vendor/anysearch.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/anysearch.js +3 -2
- package/dist/humain/web-access/vendor/anysearch.js.map +1 -1
- package/dist/humain/web-access/vendor/brave.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/brave.js +14 -4
- package/dist/humain/web-access/vendor/brave.js.map +1 -1
- package/dist/humain/web-access/vendor/curator-page.d.ts +3 -0
- package/dist/humain/web-access/vendor/curator-page.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/curator-page.js +4 -1
- package/dist/humain/web-access/vendor/curator-page.js.map +1 -1
- package/dist/humain/web-access/vendor/curator-server.d.ts +3 -0
- package/dist/humain/web-access/vendor/curator-server.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/curator-server.js +6 -0
- package/dist/humain/web-access/vendor/curator-server.js.map +1 -1
- package/dist/humain/web-access/vendor/exa.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/exa.js +16 -7
- package/dist/humain/web-access/vendor/exa.js.map +1 -1
- package/dist/humain/web-access/vendor/extract.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/extract.js +31 -3
- package/dist/humain/web-access/vendor/extract.js.map +1 -1
- package/dist/humain/web-access/vendor/gemini-search.d.ts +2 -2
- package/dist/humain/web-access/vendor/gemini-search.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/gemini-search.js +25 -4
- package/dist/humain/web-access/vendor/gemini-search.js.map +1 -1
- package/dist/humain/web-access/vendor/index.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/index.js +36 -49
- package/dist/humain/web-access/vendor/index.js.map +1 -1
- package/dist/humain/web-access/vendor/page-query.d.ts +1 -2
- package/dist/humain/web-access/vendor/page-query.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/page-query.js +5 -2
- package/dist/humain/web-access/vendor/page-query.js.map +1 -1
- package/dist/humain/web-access/vendor/parallel-mcp.d.ts +10 -0
- package/dist/humain/web-access/vendor/parallel-mcp.d.ts.map +1 -0
- package/dist/humain/web-access/vendor/parallel-mcp.js +224 -0
- package/dist/humain/web-access/vendor/parallel-mcp.js.map +1 -0
- package/dist/humain/web-access/vendor/parallel.d.ts +1 -0
- package/dist/humain/web-access/vendor/parallel.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/parallel.js +2 -2
- package/dist/humain/web-access/vendor/parallel.js.map +1 -1
- package/dist/humain/web-access/vendor/query-rewrite.d.ts +3 -0
- package/dist/humain/web-access/vendor/query-rewrite.d.ts.map +1 -0
- package/dist/humain/web-access/vendor/query-rewrite.js +42 -0
- package/dist/humain/web-access/vendor/query-rewrite.js.map +1 -0
- package/dist/humain/web-access/vendor/serper.d.ts +4 -0
- package/dist/humain/web-access/vendor/serper.d.ts.map +1 -0
- package/dist/humain/web-access/vendor/serper.js +206 -0
- package/dist/humain/web-access/vendor/serper.js.map +1 -0
- package/dist/humain/web-access/vendor/summary-model-scope.d.ts +5 -0
- package/dist/humain/web-access/vendor/summary-model-scope.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/summary-model-scope.js +10 -5
- package/dist/humain/web-access/vendor/summary-model-scope.js.map +1 -1
- package/dist/humain/web-access/vendor/summary-review.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/summary-review.js +40 -14
- package/dist/humain/web-access/vendor/summary-review.js.map +1 -1
- package/dist/humain/web-access/vendor/tavily.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/tavily.js +14 -4
- package/dist/humain/web-access/vendor/tavily.js.map +1 -1
- package/dist/humain/web-access/vendor/utils.d.ts +10 -0
- package/dist/humain/web-access/vendor/utils.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/utils.js +70 -0
- package/dist/humain/web-access/vendor/utils.js.map +1 -1
- package/dist/humain/web-access/vendor/valyu.d.ts +6 -0
- package/dist/humain/web-access/vendor/valyu.d.ts.map +1 -0
- package/dist/humain/web-access/vendor/valyu.js +194 -0
- package/dist/humain/web-access/vendor/valyu.js.map +1 -0
- package/docs/extensions.md +1 -1
- package/examples/extensions/README.md +1 -1
- package/examples/extensions/subagent/README.md +24 -147
- package/npm-shrinkwrap.json +7 -2
- package/package.json +7 -2
- package/src/humain/mcp-adapter/UPSTREAM.md +1 -1
- package/src/humain/mcp-adapter/upstream.json +3 -3
- package/src/humain/mcp-adapter/vendor/CHANGELOG.md +36 -0
- package/src/humain/web-access/UPSTREAM.md +1 -1
- package/src/humain/web-access/upstream.json +4 -4
- package/dist/bun/register-bedrock.d.ts +0 -2
- package/dist/bun/register-bedrock.d.ts.map +0 -1
- package/dist/bun/register-bedrock.js +0 -4
- package/dist/bun/register-bedrock.js.map +0 -1
- package/examples/extensions/subagent/agents.ts +0 -126
- package/examples/extensions/subagent/index.ts +0 -1015
|
@@ -8,6 +8,76 @@ export function getWebSearchConfigDir() {
|
|
|
8
8
|
export function getWebSearchConfigPath() {
|
|
9
9
|
return join(getWebSearchConfigDir(), "web-search.json");
|
|
10
10
|
}
|
|
11
|
+
export function resolveApiBaseUrl(options) {
|
|
12
|
+
const fromEnvironment = options.environmentValue !== undefined;
|
|
13
|
+
const value = fromEnvironment ? options.environmentValue : options.configuredValue;
|
|
14
|
+
if (value === undefined)
|
|
15
|
+
return options.defaultValue;
|
|
16
|
+
const source = fromEnvironment
|
|
17
|
+
? options.environmentKey
|
|
18
|
+
: `${options.configKey} in ${getWebSearchConfigPath()}`;
|
|
19
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
20
|
+
throw new Error(`${source} must be an absolute HTTP(S) URL`);
|
|
21
|
+
}
|
|
22
|
+
let url;
|
|
23
|
+
try {
|
|
24
|
+
url = new URL(value.trim());
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
throw new Error(`${source} must be an absolute HTTP(S) URL`);
|
|
28
|
+
}
|
|
29
|
+
if (url.protocol !== "https:") {
|
|
30
|
+
throw new Error(`${source} must be an absolute HTTPS URL`);
|
|
31
|
+
}
|
|
32
|
+
if (url.username || url.password) {
|
|
33
|
+
throw new Error(`${source} must not include credentials`);
|
|
34
|
+
}
|
|
35
|
+
if (url.search || url.hash) {
|
|
36
|
+
throw new Error(`${source} must not include query parameters or fragments`);
|
|
37
|
+
}
|
|
38
|
+
url.search = "";
|
|
39
|
+
url.hash = "";
|
|
40
|
+
url.pathname = url.pathname.replace(/\/+$/, "");
|
|
41
|
+
return url.toString().replace(/\/+$/, "");
|
|
42
|
+
}
|
|
43
|
+
const API_REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
44
|
+
const API_REQUEST_BODY_HEADERS = ["Content-Encoding", "Content-Language", "Content-Location", "Content-Type"];
|
|
45
|
+
const MAX_API_REDIRECTS = 5;
|
|
46
|
+
export async function fetchWithCredentialRedirects(url, init, credentialHeaders) {
|
|
47
|
+
let current = new URL(url);
|
|
48
|
+
let requestInit = init;
|
|
49
|
+
for (let redirects = 0;; redirects++) {
|
|
50
|
+
const response = await fetch(current, { ...requestInit, redirect: "manual" });
|
|
51
|
+
if (!API_REDIRECT_STATUSES.has(response.status))
|
|
52
|
+
return response;
|
|
53
|
+
const location = response.headers.get("location");
|
|
54
|
+
if (!location)
|
|
55
|
+
return response;
|
|
56
|
+
if (redirects === MAX_API_REDIRECTS) {
|
|
57
|
+
throw new Error(`Too many API redirects from ${url}`);
|
|
58
|
+
}
|
|
59
|
+
const next = new URL(location, current);
|
|
60
|
+
if (next.protocol !== "http:" && next.protocol !== "https:") {
|
|
61
|
+
throw new Error(`API redirect from ${current.origin} must use HTTP(S)`);
|
|
62
|
+
}
|
|
63
|
+
const method = requestInit.method?.toUpperCase() ?? "GET";
|
|
64
|
+
if (((response.status === 301 || response.status === 302) && method === "POST")
|
|
65
|
+
|| (response.status === 303 && method !== "GET" && method !== "HEAD")) {
|
|
66
|
+
const headers = new Headers(requestInit.headers);
|
|
67
|
+
for (const name of API_REQUEST_BODY_HEADERS)
|
|
68
|
+
headers.delete(name);
|
|
69
|
+
const { body: _body, ...withoutBody } = requestInit;
|
|
70
|
+
requestInit = { ...withoutBody, method: "GET", headers };
|
|
71
|
+
}
|
|
72
|
+
if (next.origin !== current.origin) {
|
|
73
|
+
const headers = new Headers(requestInit.headers);
|
|
74
|
+
for (const name of credentialHeaders)
|
|
75
|
+
headers.delete(name);
|
|
76
|
+
requestInit = { ...requestInit, headers };
|
|
77
|
+
}
|
|
78
|
+
current = next;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
11
81
|
const LOCAL_CURATOR_NETWORK_DEFAULTS = { enabled: false, host: "localhost", bind: "127.0.0.1" };
|
|
12
82
|
function trimmedString(value) {
|
|
13
83
|
if (typeof value !== "string")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/utils.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { hostname } from \"node:os\";\nimport { join } from \"node:path\";\nimport { getAgentDir } from \"../host.ts\";\n\nexport function getWebSearchConfigDir(): string {\n\treturn getAgentDir();\n}\n\nexport function getWebSearchConfigPath(): string {\n\treturn join(getWebSearchConfigDir(), \"web-search.json\");\n}\n\nexport interface CuratorNetworkConfig {\n\t/** Whether remote access was opted into via curatorRemote. */\n\tenabled: boolean;\n\thost: string;\n\tbind: string;\n}\n\nconst LOCAL_CURATOR_NETWORK_DEFAULTS: CuratorNetworkConfig = { enabled: false, host: \"localhost\", bind: \"127.0.0.1\" };\n\nfunction trimmedString(value: unknown): string | undefined {\n\tif (typeof value !== \"string\") return undefined;\n\tconst trimmed = value.trim();\n\treturn trimmed.length > 0 ? trimmed : undefined;\n}\n\n/** Resolves the curator server bind address and URL host from `curatorRemote`. */\nexport function resolveCuratorNetworkConfig(): CuratorNetworkConfig {\n\tconst configPath = getWebSearchConfigPath();\n\tif (!existsSync(configPath)) return LOCAL_CURATOR_NETWORK_DEFAULTS;\n\n\tlet raw: unknown;\n\ttry {\n\t\traw = JSON.parse(readFileSync(configPath, \"utf-8\"));\n\t} catch {\n\t\treturn LOCAL_CURATOR_NETWORK_DEFAULTS;\n\t}\n\tif (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return LOCAL_CURATOR_NETWORK_DEFAULTS;\n\n\tconst curatorRemote = (raw as Record<string, unknown>).curatorRemote;\n\tif (curatorRemote === true) return { enabled: true, host: hostname(), bind: \"0.0.0.0\" };\n\n\tif (curatorRemote && typeof curatorRemote === \"object\" && !Array.isArray(curatorRemote)) {\n\t\tconst obj = curatorRemote as Record<string, unknown>;\n\t\treturn {\n\t\t\tenabled: true,\n\t\t\thost: trimmedString(obj.host) ?? hostname(),\n\t\t\tbind: trimmedString(obj.bind) ?? \"0.0.0.0\",\n\t\t};\n\t}\n\n\treturn LOCAL_CURATOR_NETWORK_DEFAULTS;\n}\n\nexport function formatSeconds(s: number): string {\n\tconst h = Math.floor(s / 3600);\n\tconst m = Math.floor((s % 3600) / 60);\n\tconst sec = s % 60;\n\tif (h > 0) return `${h}:${String(m).padStart(2, \"0\")}:${String(sec).padStart(2, \"0\")}`;\n\treturn `${m}:${String(sec).padStart(2, \"0\")}`;\n}\n\nexport function readExecError(err: unknown): { code?: string; stderr: string; message: string } {\n\tif (!err || typeof err !== \"object\") {\n\t\treturn { stderr: \"\", message: String(err) };\n\t}\n\tconst code = (err as { code?: string }).code;\n\tconst message = (err as { message?: string }).message ?? \"\";\n\tconst stderrRaw = (err as { stderr?: Buffer | string }).stderr;\n\tconst stderr = Buffer.isBuffer(stderrRaw)\n\t\t? stderrRaw.toString(\"utf-8\")\n\t\t: typeof stderrRaw === \"string\"\n\t\t\t? stderrRaw\n\t\t\t: \"\";\n\treturn { code, stderr, message };\n}\n\nexport function isTimeoutError(err: unknown): boolean {\n\tif (!err || typeof err !== \"object\") return false;\n\tif ((err as { killed?: boolean }).killed) return true;\n\tconst name = (err as { name?: string }).name;\n\tconst code = (err as { code?: string }).code;\n\tconst message = (err as { message?: string }).message ?? \"\";\n\treturn name === \"AbortError\" || code === \"ETIMEDOUT\" || message.toLowerCase().includes(\"timed out\");\n}\n\nexport function trimErrorText(text: string): string {\n\treturn text.replace(/\\s+/g, \" \").trim().slice(0, 200);\n}\n\nexport function mapFfmpegError(err: unknown): string {\n\tconst { code, stderr, message } = readExecError(err);\n\tif (code === \"ENOENT\") return \"ffmpeg is not installed. Install with: brew install ffmpeg\";\n\tif (isTimeoutError(err)) return \"ffmpeg timed out extracting frame\";\n\tif (stderr.includes(\"403\")) return \"Stream URL returned 403 — may have expired, try again\";\n\tconst snippet = trimErrorText(stderr || message);\n\treturn snippet ? `ffmpeg failed: ${snippet}` : \"ffmpeg failed\";\n}\n"]}
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/utils.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { hostname } from \"node:os\";\nimport { join } from \"node:path\";\nimport { getAgentDir } from \"../host.ts\";\n\nexport function getWebSearchConfigDir(): string {\n\treturn getAgentDir();\n}\n\nexport function getWebSearchConfigPath(): string {\n\treturn join(getWebSearchConfigDir(), \"web-search.json\");\n}\n\ninterface ApiBaseUrlOptions {\n\tconfigKey: string;\n\tconfiguredValue: unknown;\n\tdefaultValue: string;\n\tenvironmentKey: string;\n\tenvironmentValue: string | undefined;\n}\n\nexport function resolveApiBaseUrl(options: ApiBaseUrlOptions): string {\n\tconst fromEnvironment = options.environmentValue !== undefined;\n\tconst value = fromEnvironment ? options.environmentValue : options.configuredValue;\n\tif (value === undefined) return options.defaultValue;\n\n\tconst source = fromEnvironment\n\t\t? options.environmentKey\n\t\t: `${options.configKey} in ${getWebSearchConfigPath()}`;\n\tif (typeof value !== \"string\" || value.trim().length === 0) {\n\t\tthrow new Error(`${source} must be an absolute HTTP(S) URL`);\n\t}\n\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(value.trim());\n\t} catch {\n\t\tthrow new Error(`${source} must be an absolute HTTP(S) URL`);\n\t}\n\tif (url.protocol !== \"https:\") {\n\t\tthrow new Error(`${source} must be an absolute HTTPS URL`);\n\t}\n\tif (url.username || url.password) {\n\t\tthrow new Error(`${source} must not include credentials`);\n\t}\n\tif (url.search || url.hash) {\n\t\tthrow new Error(`${source} must not include query parameters or fragments`);\n\t}\n\n\turl.search = \"\";\n\turl.hash = \"\";\n\turl.pathname = url.pathname.replace(/\\/+$/, \"\");\n\treturn url.toString().replace(/\\/+$/, \"\");\n}\n\nconst API_REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);\nconst API_REQUEST_BODY_HEADERS = [\"Content-Encoding\", \"Content-Language\", \"Content-Location\", \"Content-Type\"];\nconst MAX_API_REDIRECTS = 5;\n\nexport async function fetchWithCredentialRedirects(\n\turl: string,\n\tinit: RequestInit,\n\tcredentialHeaders: readonly string[],\n): Promise<Response> {\n\tlet current = new URL(url);\n\tlet requestInit = init;\n\n\tfor (let redirects = 0; ; redirects++) {\n\t\tconst response = await fetch(current, { ...requestInit, redirect: \"manual\" });\n\t\tif (!API_REDIRECT_STATUSES.has(response.status)) return response;\n\n\t\tconst location = response.headers.get(\"location\");\n\t\tif (!location) return response;\n\t\tif (redirects === MAX_API_REDIRECTS) {\n\t\t\tthrow new Error(`Too many API redirects from ${url}`);\n\t\t}\n\n\t\tconst next = new URL(location, current);\n\t\tif (next.protocol !== \"http:\" && next.protocol !== \"https:\") {\n\t\t\tthrow new Error(`API redirect from ${current.origin} must use HTTP(S)`);\n\t\t}\n\t\tconst method = requestInit.method?.toUpperCase() ?? \"GET\";\n\t\tif (\n\t\t\t((response.status === 301 || response.status === 302) && method === \"POST\")\n\t\t\t|| (response.status === 303 && method !== \"GET\" && method !== \"HEAD\")\n\t\t) {\n\t\t\tconst headers = new Headers(requestInit.headers);\n\t\t\tfor (const name of API_REQUEST_BODY_HEADERS) headers.delete(name);\n\t\t\tconst { body: _body, ...withoutBody } = requestInit;\n\t\t\trequestInit = { ...withoutBody, method: \"GET\", headers };\n\t\t}\n\t\tif (next.origin !== current.origin) {\n\t\t\tconst headers = new Headers(requestInit.headers);\n\t\t\tfor (const name of credentialHeaders) headers.delete(name);\n\t\t\trequestInit = { ...requestInit, headers };\n\t\t}\n\t\tcurrent = next;\n\t}\n}\n\nexport interface CuratorNetworkConfig {\n\t/** Whether remote access was opted into via curatorRemote. */\n\tenabled: boolean;\n\thost: string;\n\tbind: string;\n}\n\nconst LOCAL_CURATOR_NETWORK_DEFAULTS: CuratorNetworkConfig = { enabled: false, host: \"localhost\", bind: \"127.0.0.1\" };\n\nfunction trimmedString(value: unknown): string | undefined {\n\tif (typeof value !== \"string\") return undefined;\n\tconst trimmed = value.trim();\n\treturn trimmed.length > 0 ? trimmed : undefined;\n}\n\n/** Resolves the curator server bind address and URL host from `curatorRemote`. */\nexport function resolveCuratorNetworkConfig(): CuratorNetworkConfig {\n\tconst configPath = getWebSearchConfigPath();\n\tif (!existsSync(configPath)) return LOCAL_CURATOR_NETWORK_DEFAULTS;\n\n\tlet raw: unknown;\n\ttry {\n\t\traw = JSON.parse(readFileSync(configPath, \"utf-8\"));\n\t} catch {\n\t\treturn LOCAL_CURATOR_NETWORK_DEFAULTS;\n\t}\n\tif (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return LOCAL_CURATOR_NETWORK_DEFAULTS;\n\n\tconst curatorRemote = (raw as Record<string, unknown>).curatorRemote;\n\tif (curatorRemote === true) return { enabled: true, host: hostname(), bind: \"0.0.0.0\" };\n\n\tif (curatorRemote && typeof curatorRemote === \"object\" && !Array.isArray(curatorRemote)) {\n\t\tconst obj = curatorRemote as Record<string, unknown>;\n\t\treturn {\n\t\t\tenabled: true,\n\t\t\thost: trimmedString(obj.host) ?? hostname(),\n\t\t\tbind: trimmedString(obj.bind) ?? \"0.0.0.0\",\n\t\t};\n\t}\n\n\treturn LOCAL_CURATOR_NETWORK_DEFAULTS;\n}\n\nexport function formatSeconds(s: number): string {\n\tconst h = Math.floor(s / 3600);\n\tconst m = Math.floor((s % 3600) / 60);\n\tconst sec = s % 60;\n\tif (h > 0) return `${h}:${String(m).padStart(2, \"0\")}:${String(sec).padStart(2, \"0\")}`;\n\treturn `${m}:${String(sec).padStart(2, \"0\")}`;\n}\n\nexport function readExecError(err: unknown): { code?: string; stderr: string; message: string } {\n\tif (!err || typeof err !== \"object\") {\n\t\treturn { stderr: \"\", message: String(err) };\n\t}\n\tconst code = (err as { code?: string }).code;\n\tconst message = (err as { message?: string }).message ?? \"\";\n\tconst stderrRaw = (err as { stderr?: Buffer | string }).stderr;\n\tconst stderr = Buffer.isBuffer(stderrRaw)\n\t\t? stderrRaw.toString(\"utf-8\")\n\t\t: typeof stderrRaw === \"string\"\n\t\t\t? stderrRaw\n\t\t\t: \"\";\n\treturn { code, stderr, message };\n}\n\nexport function isTimeoutError(err: unknown): boolean {\n\tif (!err || typeof err !== \"object\") return false;\n\tif ((err as { killed?: boolean }).killed) return true;\n\tconst name = (err as { name?: string }).name;\n\tconst code = (err as { code?: string }).code;\n\tconst message = (err as { message?: string }).message ?? \"\";\n\treturn name === \"AbortError\" || code === \"ETIMEDOUT\" || message.toLowerCase().includes(\"timed out\");\n}\n\nexport function trimErrorText(text: string): string {\n\treturn text.replace(/\\s+/g, \" \").trim().slice(0, 200);\n}\n\nexport function mapFfmpegError(err: unknown): string {\n\tconst { code, stderr, message } = readExecError(err);\n\tif (code === \"ENOENT\") return \"ffmpeg is not installed. Install with: brew install ffmpeg\";\n\tif (isTimeoutError(err)) return \"ffmpeg timed out extracting frame\";\n\tif (stderr.includes(\"403\")) return \"Stream URL returned 403 — may have expired, try again\";\n\tconst snippet = trimErrorText(stderr || message);\n\treturn snippet ? `ffmpeg failed: ${snippet}` : \"ffmpeg failed\";\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { SearchOptions, SearchResponse } from "./perplexity.ts";
|
|
2
|
+
export declare function isValyuAvailable(): boolean;
|
|
3
|
+
export declare function searchWithValyu(query: string, options?: SearchOptions & {
|
|
4
|
+
includeContent?: boolean;
|
|
5
|
+
}): Promise<SearchResponse>;
|
|
6
|
+
//# sourceMappingURL=valyu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"valyu.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/valyu.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAsIrE,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C;AAED,wBAAsB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,aAAa,GAAG;IAAE,cAAc,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,cAAc,CAAC,CAyDxI","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { activityMonitor } from \"./activity.ts\";\nimport type { ExtractedContent } from \"./extract.ts\";\nimport type { SearchOptions, SearchResponse } from \"./perplexity.ts\";\nimport { hasCredentialSource, redactCredential, resolveCredential } from \"./credential-source.ts\";\nimport { getWebSearchConfigPath } from \"./utils.ts\";\n\nconst VALYU_SEARCH_URL = \"https://api.valyu.ai/v1/search\";\nconst CONFIG_PATH = getWebSearchConfigPath();\nconst SEARCH_TIMEOUT_MS = 60_000;\nconst MAX_SNIPPET_CHARS = 2_500;\nconst MAX_CONTENT_CHARS = 4_000;\n\ninterface WebSearchConfig {\n\tvalyuApiKey?: unknown;\n}\n\ninterface ValyuResult {\n\ttitle?: unknown;\n\turl?: unknown;\n\tdescription?: unknown;\n\tcontent?: unknown;\n}\n\nlet cachedConfig: WebSearchConfig | null = null;\n\nfunction loadConfig(): WebSearchConfig {\n\tif (cachedConfig) return cachedConfig;\n\tif (!existsSync(CONFIG_PATH)) {\n\t\tcachedConfig = {};\n\t\treturn cachedConfig;\n\t}\n\tconst raw = readFileSync(CONFIG_PATH, \"utf-8\");\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tthrow new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);\n\t}\n\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n\t\tthrow new Error(`Invalid config in ${CONFIG_PATH}: expected a JSON object`);\n\t}\n\tcachedConfig = parsed as WebSearchConfig;\n\treturn cachedConfig;\n}\n\nasync function getApiKey(signal?: AbortSignal): Promise<string | null> {\n\treturn resolveCredential({\n\t\tprovider: \"Valyu\",\n\t\tconfiguredValue: loadConfig().valyuApiKey,\n\t\tenvironmentValue: process.env.VALYU_API_KEY,\n\t\tsignal,\n\t});\n}\n\nasync function requireApiKey(signal?: AbortSignal): Promise<string> {\n\tconst apiKey = await getApiKey(signal);\n\tif (!apiKey) {\n\t\tthrow new Error(\n\t\t\t\"Valyu API key not found. Either:\\n\" +\n\t\t\t` 1. Create ${CONFIG_PATH} with { \"valyuApiKey\": \"your-key\" }\\n` +\n\t\t\t\" 2. Set VALYU_API_KEY environment variable\\n\" +\n\t\t\t\"Get a key at https://platform.valyu.ai\",\n\t\t);\n\t}\n\treturn apiKey;\n}\n\nfunction normalizeCount(value: number | undefined): number {\n\tif (typeof value !== \"number\" || !Number.isFinite(value)) return 5;\n\treturn Math.max(1, Math.min(Math.floor(value), 20));\n}\n\nfunction normalizeDomain(value: string): string | null {\n\tlet input = value.trim().toLowerCase();\n\tif (!input) return null;\n\tif (input.startsWith(\"-\")) input = input.slice(1).trim();\n\tif (!input) return null;\n\ttry {\n\t\tconst parsed = input.includes(\"://\") ? new URL(input) : new URL(`https://${input}`);\n\t\tinput = parsed.hostname;\n\t} catch {\n\t\tinput = input.split(\"/\")[0]?.split(\":\")[0] ?? \"\";\n\t}\n\tinput = input.replace(/^\\.+|\\.+$/g, \"\");\n\treturn /^[a-z0-9][a-z0-9.-]*\\.[a-z]{2,}$/i.test(input) ? input : null;\n}\n\nfunction mapDomainFilter(domainFilter: string[] | undefined): { included_sources?: string[]; excluded_sources?: string[] } {\n\tif (!domainFilter?.length) return {};\n\tconst included_sources: string[] = [];\n\tconst excluded_sources: string[] = [];\n\tfor (const raw of domainFilter) {\n\t\tconst domain = normalizeDomain(raw);\n\t\tif (!domain) continue;\n\t\tconst target = raw.trim().startsWith(\"-\") ? excluded_sources : included_sources;\n\t\tif (!target.includes(domain)) target.push(domain);\n\t}\n\treturn {\n\t\t...(included_sources.length > 0 ? { included_sources } : {}),\n\t\t...(excluded_sources.length > 0 ? { excluded_sources } : {}),\n\t};\n}\n\nfunction recencyToStartDate(filter: SearchOptions[\"recencyFilter\"]): string | undefined {\n\tif (!filter) return undefined;\n\tconst days = { day: 1, week: 7, month: 30, year: 365 }[filter];\n\treturn days ? new Date(Date.now() - days * 86_400_000).toISOString().slice(0, 10) : undefined;\n}\n\nfunction text(value: unknown, limit: number): string {\n\treturn typeof value === \"string\" ? value.replace(/\\s+/g, \" \").trim().slice(0, limit) : \"\";\n}\n\nfunction errorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction invalidResponse(message: string): Error {\n\treturn new Error(`Valyu API returned invalid response: ${message}`);\n}\n\nfunction parseResponse(value: unknown): ValyuResult[] {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) throw invalidResponse(\"expected an object envelope\");\n\tconst envelope = value as Record<string, unknown>;\n\tif (envelope.success !== true) throw invalidResponse(\"expected success true\");\n\tif (!Array.isArray(envelope.results)) throw invalidResponse(\"expected results array\");\n\treturn envelope.results as ValyuResult[];\n}\n\nfunction buildAnswer(results: SearchResponse[\"results\"]): string {\n\treturn results.map((result) => result.snippet\n\t\t? `${result.snippet}\\nSource: ${result.title} (${result.url})`\n\t\t: `Source: ${result.title} (${result.url})`).join(\"\\n\\n\");\n}\n\nexport function isValyuAvailable(): boolean {\n\treturn hasCredentialSource({ provider: \"Valyu\", configuredValue: loadConfig().valyuApiKey, environmentValue: process.env.VALYU_API_KEY });\n}\n\nexport async function searchWithValyu(query: string, options: SearchOptions & { includeContent?: boolean } = {}): Promise<SearchResponse> {\n\tconst apiKey = await requireApiKey(options.signal);\n\tconst numResults = normalizeCount(options.numResults);\n\tconst startDate = recencyToStartDate(options.recencyFilter);\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(VALYU_SEARCH_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"x-api-key\": apiKey, \"Content-Type\": \"application/json\", Accept: \"application/json\" },\n\t\t\tbody: JSON.stringify({ query, max_num_results: numResults, ...mapDomainFilter(options.domainFilter), ...(startDate ? { start_date: startDate } : {}) }),\n\t\t\tsignal: options.signal ? AbortSignal.any([AbortSignal.timeout(SEARCH_TIMEOUT_MS), options.signal]) : AbortSignal.timeout(SEARCH_TIMEOUT_MS),\n\t\t});\n\t} catch (err) {\n\t\tconst message = errorMessage(err);\n\t\tconst redactedMessage = redactCredential(message, apiKey);\n\t\tif (redactedMessage.toLowerCase().includes(\"abort\")) activityMonitor.logComplete(activityId, 0);\n\t\telse activityMonitor.logError(activityId, redactedMessage);\n\t\tif (redactedMessage === message) throw err;\n\t\tconst redactedError = new Error(redactedMessage);\n\t\tif (err instanceof Error) redactedError.name = err.name;\n\t\tthrow redactedError;\n\t}\n\tif (!response.ok) {\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tconst errorText = redactCredential(await response.text(), apiKey);\n\t\tthrow new Error(`Valyu API error ${response.status}: ${errorText.slice(0, 300)}`);\n\t}\n\tlet rawData: unknown;\n\ttry {\n\t\trawData = await response.json();\n\t} catch (err) {\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tthrow new Error(`Valyu API returned invalid JSON: ${errorMessage(err)}`);\n\t}\n\tlet entries: ValyuResult[];\n\ttry {\n\t\tentries = parseResponse(rawData);\n\t} catch (err) {\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tthrow err;\n\t}\n\tactivityMonitor.logComplete(activityId, response.status);\n\tconst results: SearchResponse[\"results\"] = [];\n\tconst inlineContent: ExtractedContent[] = [];\n\tfor (const entry of entries) {\n\t\tif (!entry || typeof entry !== \"object\") continue;\n\t\tconst url = text(entry.url, Number.MAX_SAFE_INTEGER);\n\t\tif (!url) continue;\n\t\tconst title = text(entry.title, Number.MAX_SAFE_INTEGER) || `Source ${results.length + 1}`;\n\t\tconst content = text(entry.content, MAX_CONTENT_CHARS);\n\t\tconst description = text(entry.description, MAX_SNIPPET_CHARS);\n\t\tresults.push({ title, url, snippet: (content || description).slice(0, MAX_SNIPPET_CHARS) });\n\t\tif (options.includeContent && content) inlineContent.push({ url, title, content, error: null });\n\t\tif (results.length >= numResults) break;\n\t}\n\treturn { answer: buildAnswer(results), results, ...(inlineContent.length > 0 ? { inlineContent } : {}) };\n}\n"]}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { activityMonitor } from "./activity.js";
|
|
3
|
+
import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.js";
|
|
4
|
+
import { getWebSearchConfigPath } from "./utils.js";
|
|
5
|
+
const VALYU_SEARCH_URL = "https://api.valyu.ai/v1/search";
|
|
6
|
+
const CONFIG_PATH = getWebSearchConfigPath();
|
|
7
|
+
const SEARCH_TIMEOUT_MS = 60_000;
|
|
8
|
+
const MAX_SNIPPET_CHARS = 2_500;
|
|
9
|
+
const MAX_CONTENT_CHARS = 4_000;
|
|
10
|
+
let cachedConfig = null;
|
|
11
|
+
function loadConfig() {
|
|
12
|
+
if (cachedConfig)
|
|
13
|
+
return cachedConfig;
|
|
14
|
+
if (!existsSync(CONFIG_PATH)) {
|
|
15
|
+
cachedConfig = {};
|
|
16
|
+
return cachedConfig;
|
|
17
|
+
}
|
|
18
|
+
const raw = readFileSync(CONFIG_PATH, "utf-8");
|
|
19
|
+
let parsed;
|
|
20
|
+
try {
|
|
21
|
+
parsed = JSON.parse(raw);
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
25
|
+
throw new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);
|
|
26
|
+
}
|
|
27
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
28
|
+
throw new Error(`Invalid config in ${CONFIG_PATH}: expected a JSON object`);
|
|
29
|
+
}
|
|
30
|
+
cachedConfig = parsed;
|
|
31
|
+
return cachedConfig;
|
|
32
|
+
}
|
|
33
|
+
async function getApiKey(signal) {
|
|
34
|
+
return resolveCredential({
|
|
35
|
+
provider: "Valyu",
|
|
36
|
+
configuredValue: loadConfig().valyuApiKey,
|
|
37
|
+
environmentValue: process.env.VALYU_API_KEY,
|
|
38
|
+
signal,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
async function requireApiKey(signal) {
|
|
42
|
+
const apiKey = await getApiKey(signal);
|
|
43
|
+
if (!apiKey) {
|
|
44
|
+
throw new Error("Valyu API key not found. Either:\n" +
|
|
45
|
+
` 1. Create ${CONFIG_PATH} with { "valyuApiKey": "your-key" }\n` +
|
|
46
|
+
" 2. Set VALYU_API_KEY environment variable\n" +
|
|
47
|
+
"Get a key at https://platform.valyu.ai");
|
|
48
|
+
}
|
|
49
|
+
return apiKey;
|
|
50
|
+
}
|
|
51
|
+
function normalizeCount(value) {
|
|
52
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
53
|
+
return 5;
|
|
54
|
+
return Math.max(1, Math.min(Math.floor(value), 20));
|
|
55
|
+
}
|
|
56
|
+
function normalizeDomain(value) {
|
|
57
|
+
let input = value.trim().toLowerCase();
|
|
58
|
+
if (!input)
|
|
59
|
+
return null;
|
|
60
|
+
if (input.startsWith("-"))
|
|
61
|
+
input = input.slice(1).trim();
|
|
62
|
+
if (!input)
|
|
63
|
+
return null;
|
|
64
|
+
try {
|
|
65
|
+
const parsed = input.includes("://") ? new URL(input) : new URL(`https://${input}`);
|
|
66
|
+
input = parsed.hostname;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
input = input.split("/")[0]?.split(":")[0] ?? "";
|
|
70
|
+
}
|
|
71
|
+
input = input.replace(/^\.+|\.+$/g, "");
|
|
72
|
+
return /^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(input) ? input : null;
|
|
73
|
+
}
|
|
74
|
+
function mapDomainFilter(domainFilter) {
|
|
75
|
+
if (!domainFilter?.length)
|
|
76
|
+
return {};
|
|
77
|
+
const included_sources = [];
|
|
78
|
+
const excluded_sources = [];
|
|
79
|
+
for (const raw of domainFilter) {
|
|
80
|
+
const domain = normalizeDomain(raw);
|
|
81
|
+
if (!domain)
|
|
82
|
+
continue;
|
|
83
|
+
const target = raw.trim().startsWith("-") ? excluded_sources : included_sources;
|
|
84
|
+
if (!target.includes(domain))
|
|
85
|
+
target.push(domain);
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
...(included_sources.length > 0 ? { included_sources } : {}),
|
|
89
|
+
...(excluded_sources.length > 0 ? { excluded_sources } : {}),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function recencyToStartDate(filter) {
|
|
93
|
+
if (!filter)
|
|
94
|
+
return undefined;
|
|
95
|
+
const days = { day: 1, week: 7, month: 30, year: 365 }[filter];
|
|
96
|
+
return days ? new Date(Date.now() - days * 86_400_000).toISOString().slice(0, 10) : undefined;
|
|
97
|
+
}
|
|
98
|
+
function text(value, limit) {
|
|
99
|
+
return typeof value === "string" ? value.replace(/\s+/g, " ").trim().slice(0, limit) : "";
|
|
100
|
+
}
|
|
101
|
+
function errorMessage(err) {
|
|
102
|
+
return err instanceof Error ? err.message : String(err);
|
|
103
|
+
}
|
|
104
|
+
function invalidResponse(message) {
|
|
105
|
+
return new Error(`Valyu API returned invalid response: ${message}`);
|
|
106
|
+
}
|
|
107
|
+
function parseResponse(value) {
|
|
108
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
109
|
+
throw invalidResponse("expected an object envelope");
|
|
110
|
+
const envelope = value;
|
|
111
|
+
if (envelope.success !== true)
|
|
112
|
+
throw invalidResponse("expected success true");
|
|
113
|
+
if (!Array.isArray(envelope.results))
|
|
114
|
+
throw invalidResponse("expected results array");
|
|
115
|
+
return envelope.results;
|
|
116
|
+
}
|
|
117
|
+
function buildAnswer(results) {
|
|
118
|
+
return results.map((result) => result.snippet
|
|
119
|
+
? `${result.snippet}\nSource: ${result.title} (${result.url})`
|
|
120
|
+
: `Source: ${result.title} (${result.url})`).join("\n\n");
|
|
121
|
+
}
|
|
122
|
+
export function isValyuAvailable() {
|
|
123
|
+
return hasCredentialSource({ provider: "Valyu", configuredValue: loadConfig().valyuApiKey, environmentValue: process.env.VALYU_API_KEY });
|
|
124
|
+
}
|
|
125
|
+
export async function searchWithValyu(query, options = {}) {
|
|
126
|
+
const apiKey = await requireApiKey(options.signal);
|
|
127
|
+
const numResults = normalizeCount(options.numResults);
|
|
128
|
+
const startDate = recencyToStartDate(options.recencyFilter);
|
|
129
|
+
const activityId = activityMonitor.logStart({ type: "api", query });
|
|
130
|
+
let response;
|
|
131
|
+
try {
|
|
132
|
+
response = await fetch(VALYU_SEARCH_URL, {
|
|
133
|
+
method: "POST",
|
|
134
|
+
headers: { "x-api-key": apiKey, "Content-Type": "application/json", Accept: "application/json" },
|
|
135
|
+
body: JSON.stringify({ query, max_num_results: numResults, ...mapDomainFilter(options.domainFilter), ...(startDate ? { start_date: startDate } : {}) }),
|
|
136
|
+
signal: options.signal ? AbortSignal.any([AbortSignal.timeout(SEARCH_TIMEOUT_MS), options.signal]) : AbortSignal.timeout(SEARCH_TIMEOUT_MS),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
const message = errorMessage(err);
|
|
141
|
+
const redactedMessage = redactCredential(message, apiKey);
|
|
142
|
+
if (redactedMessage.toLowerCase().includes("abort"))
|
|
143
|
+
activityMonitor.logComplete(activityId, 0);
|
|
144
|
+
else
|
|
145
|
+
activityMonitor.logError(activityId, redactedMessage);
|
|
146
|
+
if (redactedMessage === message)
|
|
147
|
+
throw err;
|
|
148
|
+
const redactedError = new Error(redactedMessage);
|
|
149
|
+
if (err instanceof Error)
|
|
150
|
+
redactedError.name = err.name;
|
|
151
|
+
throw redactedError;
|
|
152
|
+
}
|
|
153
|
+
if (!response.ok) {
|
|
154
|
+
activityMonitor.logComplete(activityId, response.status);
|
|
155
|
+
const errorText = redactCredential(await response.text(), apiKey);
|
|
156
|
+
throw new Error(`Valyu API error ${response.status}: ${errorText.slice(0, 300)}`);
|
|
157
|
+
}
|
|
158
|
+
let rawData;
|
|
159
|
+
try {
|
|
160
|
+
rawData = await response.json();
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
activityMonitor.logComplete(activityId, response.status);
|
|
164
|
+
throw new Error(`Valyu API returned invalid JSON: ${errorMessage(err)}`);
|
|
165
|
+
}
|
|
166
|
+
let entries;
|
|
167
|
+
try {
|
|
168
|
+
entries = parseResponse(rawData);
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
activityMonitor.logComplete(activityId, response.status);
|
|
172
|
+
throw err;
|
|
173
|
+
}
|
|
174
|
+
activityMonitor.logComplete(activityId, response.status);
|
|
175
|
+
const results = [];
|
|
176
|
+
const inlineContent = [];
|
|
177
|
+
for (const entry of entries) {
|
|
178
|
+
if (!entry || typeof entry !== "object")
|
|
179
|
+
continue;
|
|
180
|
+
const url = text(entry.url, Number.MAX_SAFE_INTEGER);
|
|
181
|
+
if (!url)
|
|
182
|
+
continue;
|
|
183
|
+
const title = text(entry.title, Number.MAX_SAFE_INTEGER) || `Source ${results.length + 1}`;
|
|
184
|
+
const content = text(entry.content, MAX_CONTENT_CHARS);
|
|
185
|
+
const description = text(entry.description, MAX_SNIPPET_CHARS);
|
|
186
|
+
results.push({ title, url, snippet: (content || description).slice(0, MAX_SNIPPET_CHARS) });
|
|
187
|
+
if (options.includeContent && content)
|
|
188
|
+
inlineContent.push({ url, title, content, error: null });
|
|
189
|
+
if (results.length >= numResults)
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
return { answer: buildAnswer(results), results, ...(inlineContent.length > 0 ? { inlineContent } : {}) };
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=valyu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"valyu.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/valyu.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGhD,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAClG,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEpD,MAAM,gBAAgB,GAAG,gCAAgC,CAAC;AAC1D,MAAM,WAAW,GAAG,sBAAsB,EAAE,CAAC;AAC7C,MAAM,iBAAiB,GAAG,MAAM,CAAC;AACjC,MAAM,iBAAiB,GAAG,KAAK,CAAC;AAChC,MAAM,iBAAiB,GAAG,KAAK,CAAC;AAahC,IAAI,YAAY,GAA2B,IAAI,CAAC;AAEhD,SAAS,UAAU,GAAoB;IACtC,IAAI,YAAY;QAAE,OAAO,YAAY,CAAC;IACtC,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC9B,YAAY,GAAG,EAAE,CAAC;QAClB,OAAO,YAAY,CAAC;IACrB,CAAC;IACD,MAAM,GAAG,GAAG,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IAC/C,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACJ,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,MAAM,IAAI,KAAK,CAAC,mBAAmB,WAAW,KAAK,OAAO,EAAE,CAAC,CAAC;IAC/D,CAAC;IACD,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACpE,MAAM,IAAI,KAAK,CAAC,qBAAqB,WAAW,0BAA0B,CAAC,CAAC;IAC7E,CAAC;IACD,YAAY,GAAG,MAAyB,CAAC;IACzC,OAAO,YAAY,CAAC;AAAA,CACpB;AAED,KAAK,UAAU,SAAS,CAAC,MAAoB,EAA0B;IACtE,OAAO,iBAAiB,CAAC;QACxB,QAAQ,EAAE,OAAO;QACjB,eAAe,EAAE,UAAU,EAAE,CAAC,WAAW;QACzC,gBAAgB,EAAE,OAAO,CAAC,GAAG,CAAC,aAAa;QAC3C,MAAM;KACN,CAAC,CAAC;AAAA,CACH;AAED,KAAK,UAAU,aAAa,CAAC,MAAoB,EAAmB;IACnE,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,CAAC;IACvC,IAAI,CAAC,MAAM,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACd,oCAAoC;YACpC,eAAe,WAAW,uCAAuC;YACjE,+CAA+C;YAC/C,wCAAwC,CACxC,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAAA,CACd;AAED,SAAS,cAAc,CAAC,KAAyB,EAAU;IAC1D,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACnE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;AAAA,CACpD;AAED,SAAS,eAAe,CAAC,KAAa,EAAiB;IACtD,IAAI,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACvC,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACzD,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,WAAW,KAAK,EAAE,CAAC,CAAC;QACpF,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACR,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAClD,CAAC;IACD,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IACxC,OAAO,mCAAmC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AAAA,CACtE;AAED,SAAS,eAAe,CAAC,YAAkC,EAAgE;IAC1H,IAAI,CAAC,YAAY,EAAE,MAAM;QAAE,OAAO,EAAE,CAAC;IACrC,MAAM,gBAAgB,GAAa,EAAE,CAAC;IACtC,MAAM,gBAAgB,GAAa,EAAE,CAAC;IACtC,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;QAChC,MAAM,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,CAAC,MAAM;YAAE,SAAS;QACtB,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,gBAAgB,CAAC;QAChF,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACnD,CAAC;IACD,OAAO;QACN,GAAG,CAAC,gBAAgB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5D,GAAG,CAAC,gBAAgB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5D,CAAC;AAAA,CACF;AAED,SAAS,kBAAkB,CAAC,MAAsC,EAAsB;IACvF,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAC9B,MAAM,IAAI,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC;IAC/D,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,GAAG,UAAU,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAAA,CAC9F;AAED,SAAS,IAAI,CAAC,KAAc,EAAE,KAAa,EAAU;IACpD,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAAA,CAC1F;AAED,SAAS,YAAY,CAAC,GAAY,EAAU;IAC3C,OAAO,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AAAA,CACxD;AAED,SAAS,eAAe,CAAC,OAAe,EAAS;IAChD,OAAO,IAAI,KAAK,CAAC,wCAAwC,OAAO,EAAE,CAAC,CAAC;AAAA,CACpE;AAED,SAAS,aAAa,CAAC,KAAc,EAAiB;IACrD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,MAAM,eAAe,CAAC,6BAA6B,CAAC,CAAC;IACtH,MAAM,QAAQ,GAAG,KAAgC,CAAC;IAClD,IAAI,QAAQ,CAAC,OAAO,KAAK,IAAI;QAAE,MAAM,eAAe,CAAC,uBAAuB,CAAC,CAAC;IAC9E,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,MAAM,eAAe,CAAC,wBAAwB,CAAC,CAAC;IACtF,OAAO,QAAQ,CAAC,OAAwB,CAAC;AAAA,CACzC;AAED,SAAS,WAAW,CAAC,OAAkC,EAAU;IAChE,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO;QAC5C,CAAC,CAAC,GAAG,MAAM,CAAC,OAAO,aAAa,MAAM,CAAC,KAAK,KAAK,MAAM,CAAC,GAAG,GAAG;QAC9D,CAAC,CAAC,WAAW,MAAM,CAAC,KAAK,KAAK,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAAA,CAC3D;AAED,MAAM,UAAU,gBAAgB,GAAY;IAC3C,OAAO,mBAAmB,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,CAAC,WAAW,EAAE,gBAAgB,EAAE,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC,CAAC;AAAA,CAC1I;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,KAAa,EAAE,OAAO,GAAiD,EAAE,EAA2B;IACzI,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACnD,MAAM,UAAU,GAAG,cAAc,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACtD,MAAM,SAAS,GAAG,kBAAkB,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IAC5D,MAAM,UAAU,GAAG,eAAe,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IACpE,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACJ,QAAQ,GAAG,MAAM,KAAK,CAAC,gBAAgB,EAAE;YACxC,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,EAAE,kBAAkB,EAAE;YAChG,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,GAAG,eAAe,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;YACvJ,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,iBAAiB,CAAC;SAC3I,CAAC,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,MAAM,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;QAClC,MAAM,eAAe,GAAG,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC1D,IAAI,eAAe,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC;YAAE,eAAe,CAAC,WAAW,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;;YAC3F,eAAe,CAAC,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;QAC3D,IAAI,eAAe,KAAK,OAAO;YAAE,MAAM,GAAG,CAAC;QAC3C,MAAM,aAAa,GAAG,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC;QACjD,IAAI,GAAG,YAAY,KAAK;YAAE,aAAa,CAAC,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;QACxD,MAAM,aAAa,CAAC;IACrB,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QAClB,eAAe,CAAC,WAAW,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;QACzD,MAAM,SAAS,GAAG,gBAAgB,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC;QAClE,MAAM,IAAI,KAAK,CAAC,mBAAmB,QAAQ,CAAC,MAAM,KAAK,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IACnF,CAAC;IACD,IAAI,OAAgB,CAAC;IACrB,IAAI,CAAC;QACJ,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IACjC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,eAAe,CAAC,WAAW,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;QACzD,MAAM,IAAI,KAAK,CAAC,oCAAoC,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC1E,CAAC;IACD,IAAI,OAAsB,CAAC;IAC3B,IAAI,CAAC;QACJ,OAAO,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,eAAe,CAAC,WAAW,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;QACzD,MAAM,GAAG,CAAC;IACX,CAAC;IACD,eAAe,CAAC,WAAW,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACzD,MAAM,OAAO,GAA8B,EAAE,CAAC;IAC9C,MAAM,aAAa,GAAuB,EAAE,CAAC;IAC7C,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,SAAS;QAClD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,gBAAgB,CAAC,CAAC;QACrD,IAAI,CAAC,GAAG;YAAE,SAAS;QACnB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,gBAAgB,CAAC,IAAI,UAAU,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3F,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC;QACvD,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,iBAAiB,CAAC,CAAC;QAC/D,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,OAAO,IAAI,WAAW,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,iBAAiB,CAAC,EAAE,CAAC,CAAC;QAC5F,IAAI,OAAO,CAAC,cAAc,IAAI,OAAO;YAAE,aAAa,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAChG,IAAI,OAAO,CAAC,MAAM,IAAI,UAAU;YAAE,MAAM;IACzC,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AAAA,CACzG","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { activityMonitor } from \"./activity.ts\";\nimport type { ExtractedContent } from \"./extract.ts\";\nimport type { SearchOptions, SearchResponse } from \"./perplexity.ts\";\nimport { hasCredentialSource, redactCredential, resolveCredential } from \"./credential-source.ts\";\nimport { getWebSearchConfigPath } from \"./utils.ts\";\n\nconst VALYU_SEARCH_URL = \"https://api.valyu.ai/v1/search\";\nconst CONFIG_PATH = getWebSearchConfigPath();\nconst SEARCH_TIMEOUT_MS = 60_000;\nconst MAX_SNIPPET_CHARS = 2_500;\nconst MAX_CONTENT_CHARS = 4_000;\n\ninterface WebSearchConfig {\n\tvalyuApiKey?: unknown;\n}\n\ninterface ValyuResult {\n\ttitle?: unknown;\n\turl?: unknown;\n\tdescription?: unknown;\n\tcontent?: unknown;\n}\n\nlet cachedConfig: WebSearchConfig | null = null;\n\nfunction loadConfig(): WebSearchConfig {\n\tif (cachedConfig) return cachedConfig;\n\tif (!existsSync(CONFIG_PATH)) {\n\t\tcachedConfig = {};\n\t\treturn cachedConfig;\n\t}\n\tconst raw = readFileSync(CONFIG_PATH, \"utf-8\");\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tthrow new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);\n\t}\n\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n\t\tthrow new Error(`Invalid config in ${CONFIG_PATH}: expected a JSON object`);\n\t}\n\tcachedConfig = parsed as WebSearchConfig;\n\treturn cachedConfig;\n}\n\nasync function getApiKey(signal?: AbortSignal): Promise<string | null> {\n\treturn resolveCredential({\n\t\tprovider: \"Valyu\",\n\t\tconfiguredValue: loadConfig().valyuApiKey,\n\t\tenvironmentValue: process.env.VALYU_API_KEY,\n\t\tsignal,\n\t});\n}\n\nasync function requireApiKey(signal?: AbortSignal): Promise<string> {\n\tconst apiKey = await getApiKey(signal);\n\tif (!apiKey) {\n\t\tthrow new Error(\n\t\t\t\"Valyu API key not found. Either:\\n\" +\n\t\t\t` 1. Create ${CONFIG_PATH} with { \"valyuApiKey\": \"your-key\" }\\n` +\n\t\t\t\" 2. Set VALYU_API_KEY environment variable\\n\" +\n\t\t\t\"Get a key at https://platform.valyu.ai\",\n\t\t);\n\t}\n\treturn apiKey;\n}\n\nfunction normalizeCount(value: number | undefined): number {\n\tif (typeof value !== \"number\" || !Number.isFinite(value)) return 5;\n\treturn Math.max(1, Math.min(Math.floor(value), 20));\n}\n\nfunction normalizeDomain(value: string): string | null {\n\tlet input = value.trim().toLowerCase();\n\tif (!input) return null;\n\tif (input.startsWith(\"-\")) input = input.slice(1).trim();\n\tif (!input) return null;\n\ttry {\n\t\tconst parsed = input.includes(\"://\") ? new URL(input) : new URL(`https://${input}`);\n\t\tinput = parsed.hostname;\n\t} catch {\n\t\tinput = input.split(\"/\")[0]?.split(\":\")[0] ?? \"\";\n\t}\n\tinput = input.replace(/^\\.+|\\.+$/g, \"\");\n\treturn /^[a-z0-9][a-z0-9.-]*\\.[a-z]{2,}$/i.test(input) ? input : null;\n}\n\nfunction mapDomainFilter(domainFilter: string[] | undefined): { included_sources?: string[]; excluded_sources?: string[] } {\n\tif (!domainFilter?.length) return {};\n\tconst included_sources: string[] = [];\n\tconst excluded_sources: string[] = [];\n\tfor (const raw of domainFilter) {\n\t\tconst domain = normalizeDomain(raw);\n\t\tif (!domain) continue;\n\t\tconst target = raw.trim().startsWith(\"-\") ? excluded_sources : included_sources;\n\t\tif (!target.includes(domain)) target.push(domain);\n\t}\n\treturn {\n\t\t...(included_sources.length > 0 ? { included_sources } : {}),\n\t\t...(excluded_sources.length > 0 ? { excluded_sources } : {}),\n\t};\n}\n\nfunction recencyToStartDate(filter: SearchOptions[\"recencyFilter\"]): string | undefined {\n\tif (!filter) return undefined;\n\tconst days = { day: 1, week: 7, month: 30, year: 365 }[filter];\n\treturn days ? new Date(Date.now() - days * 86_400_000).toISOString().slice(0, 10) : undefined;\n}\n\nfunction text(value: unknown, limit: number): string {\n\treturn typeof value === \"string\" ? value.replace(/\\s+/g, \" \").trim().slice(0, limit) : \"\";\n}\n\nfunction errorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction invalidResponse(message: string): Error {\n\treturn new Error(`Valyu API returned invalid response: ${message}`);\n}\n\nfunction parseResponse(value: unknown): ValyuResult[] {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) throw invalidResponse(\"expected an object envelope\");\n\tconst envelope = value as Record<string, unknown>;\n\tif (envelope.success !== true) throw invalidResponse(\"expected success true\");\n\tif (!Array.isArray(envelope.results)) throw invalidResponse(\"expected results array\");\n\treturn envelope.results as ValyuResult[];\n}\n\nfunction buildAnswer(results: SearchResponse[\"results\"]): string {\n\treturn results.map((result) => result.snippet\n\t\t? `${result.snippet}\\nSource: ${result.title} (${result.url})`\n\t\t: `Source: ${result.title} (${result.url})`).join(\"\\n\\n\");\n}\n\nexport function isValyuAvailable(): boolean {\n\treturn hasCredentialSource({ provider: \"Valyu\", configuredValue: loadConfig().valyuApiKey, environmentValue: process.env.VALYU_API_KEY });\n}\n\nexport async function searchWithValyu(query: string, options: SearchOptions & { includeContent?: boolean } = {}): Promise<SearchResponse> {\n\tconst apiKey = await requireApiKey(options.signal);\n\tconst numResults = normalizeCount(options.numResults);\n\tconst startDate = recencyToStartDate(options.recencyFilter);\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(VALYU_SEARCH_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"x-api-key\": apiKey, \"Content-Type\": \"application/json\", Accept: \"application/json\" },\n\t\t\tbody: JSON.stringify({ query, max_num_results: numResults, ...mapDomainFilter(options.domainFilter), ...(startDate ? { start_date: startDate } : {}) }),\n\t\t\tsignal: options.signal ? AbortSignal.any([AbortSignal.timeout(SEARCH_TIMEOUT_MS), options.signal]) : AbortSignal.timeout(SEARCH_TIMEOUT_MS),\n\t\t});\n\t} catch (err) {\n\t\tconst message = errorMessage(err);\n\t\tconst redactedMessage = redactCredential(message, apiKey);\n\t\tif (redactedMessage.toLowerCase().includes(\"abort\")) activityMonitor.logComplete(activityId, 0);\n\t\telse activityMonitor.logError(activityId, redactedMessage);\n\t\tif (redactedMessage === message) throw err;\n\t\tconst redactedError = new Error(redactedMessage);\n\t\tif (err instanceof Error) redactedError.name = err.name;\n\t\tthrow redactedError;\n\t}\n\tif (!response.ok) {\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tconst errorText = redactCredential(await response.text(), apiKey);\n\t\tthrow new Error(`Valyu API error ${response.status}: ${errorText.slice(0, 300)}`);\n\t}\n\tlet rawData: unknown;\n\ttry {\n\t\trawData = await response.json();\n\t} catch (err) {\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tthrow new Error(`Valyu API returned invalid JSON: ${errorMessage(err)}`);\n\t}\n\tlet entries: ValyuResult[];\n\ttry {\n\t\tentries = parseResponse(rawData);\n\t} catch (err) {\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tthrow err;\n\t}\n\tactivityMonitor.logComplete(activityId, response.status);\n\tconst results: SearchResponse[\"results\"] = [];\n\tconst inlineContent: ExtractedContent[] = [];\n\tfor (const entry of entries) {\n\t\tif (!entry || typeof entry !== \"object\") continue;\n\t\tconst url = text(entry.url, Number.MAX_SAFE_INTEGER);\n\t\tif (!url) continue;\n\t\tconst title = text(entry.title, Number.MAX_SAFE_INTEGER) || `Source ${results.length + 1}`;\n\t\tconst content = text(entry.content, MAX_CONTENT_CHARS);\n\t\tconst description = text(entry.description, MAX_SNIPPET_CHARS);\n\t\tresults.push({ title, url, snippet: (content || description).slice(0, MAX_SNIPPET_CHARS) });\n\t\tif (options.includeContent && content) inlineContent.push({ url, title, content, error: null });\n\t\tif (results.length >= numResults) break;\n\t}\n\treturn { answer: buildAnswer(results), results, ...(inlineContent.length > 0 ? { inlineContent } : {}) };\n}\n"]}
|
package/docs/extensions.md
CHANGED
|
@@ -2968,7 +2968,7 @@ All examples in [examples/extensions/](../examples/extensions/).
|
|
|
2968
2968
|
| `interactive-shell.ts` | Persistent shell session | `on("user_bash")` |
|
|
2969
2969
|
| `sandbox/` | Sandboxed tool execution | Tool operations |
|
|
2970
2970
|
| `gondolin/` | Route built-in tools and `!` commands into a Gondolin micro-VM | Tool operations, built-in tool overrides, `on("user_bash")` |
|
|
2971
|
-
| `subagent/` |
|
|
2971
|
+
| `subagent/` | Sample agent definitions for the built-in `subagent` tool | agent/prompt markdown samples |
|
|
2972
2972
|
| **Games** |||
|
|
2973
2973
|
| `snake.ts` | Snake game | `registerCommand`, `ui.custom`, keyboard handling |
|
|
2974
2974
|
| `space-invaders.ts` | Space Invaders game | `registerCommand`, `ui.custom` |
|
|
@@ -42,7 +42,7 @@ cp permission-gate.ts ~/.humain-terminal/agent/extensions/
|
|
|
42
42
|
| `minimal-mode.ts` | Override built-in tool rendering for minimal display (only tool calls, no output in collapsed mode) |
|
|
43
43
|
| `truncated-tool.ts` | Wraps ripgrep with proper output truncation (50KB/2000 lines) |
|
|
44
44
|
| `ssh.ts` | Delegate all tools to a remote machine via SSH using pluggable operations |
|
|
45
|
-
| `subagent/` |
|
|
45
|
+
| `subagent/` | Sample agent definitions and workflow presets for the built-in `subagent` tool |
|
|
46
46
|
|
|
47
47
|
### Commands & UI
|
|
48
48
|
|
|
@@ -1,23 +1,26 @@
|
|
|
1
|
-
# Subagent
|
|
1
|
+
# Subagent Agent Definitions
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The `subagent` tool is now a built-in core tool (see
|
|
4
|
+
`packages/coding-agent/src/core/tools/subagent.ts`) — no extension install is
|
|
5
|
+
required. It delegates tasks to specialized subagents with isolated context
|
|
6
|
+
windows, in three modes:
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
- **Single**: `{ agent: "name", task: "..." }`
|
|
9
|
+
- **Parallel**: `{ tasks: [{ agent, task }, ...] }` (max 8 tasks, up to 4 concurrent)
|
|
10
|
+
- **Chain**: `{ chain: [{ agent, task }, ...] }` with a `{previous}` placeholder
|
|
11
|
+
that carries the prior step's output
|
|
6
12
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
- **Abort support**: Ctrl+C propagates to kill subagent processes
|
|
13
|
+
Subagents cannot delegate further (nesting is blocked via
|
|
14
|
+
`HUMAIN_TERMINAL_SUBAGENT_DEPTH`).
|
|
15
|
+
|
|
16
|
+
This directory keeps sample agent definitions and workflow prompt presets you
|
|
17
|
+
can copy into your own setup.
|
|
13
18
|
|
|
14
19
|
## Structure
|
|
15
20
|
|
|
16
21
|
```
|
|
17
22
|
subagent/
|
|
18
23
|
├── README.md # This file
|
|
19
|
-
├── index.ts # The extension (entry point)
|
|
20
|
-
├── agents.ts # Agent discovery logic
|
|
21
24
|
├── agents/ # Sample agent definitions
|
|
22
25
|
│ ├── scout.md # Fast recon, returns compressed context
|
|
23
26
|
│ ├── planner.md # Creates implementation plans
|
|
@@ -29,147 +32,21 @@ subagent/
|
|
|
29
32
|
└── implement-and-review.md # worker -> reviewer -> worker
|
|
30
33
|
```
|
|
31
34
|
|
|
32
|
-
##
|
|
35
|
+
## Installing the samples
|
|
33
36
|
|
|
34
|
-
|
|
37
|
+
Agent definitions are markdown files with `name`, `description`, optional
|
|
38
|
+
`tools` (comma-separated) and `model` frontmatter; the body becomes the
|
|
39
|
+
subagent's appended system prompt.
|
|
35
40
|
|
|
36
41
|
```bash
|
|
37
|
-
#
|
|
38
|
-
mkdir -p ~/.humain-terminal/agent/extensions/subagent
|
|
39
|
-
ln -sf "$(pwd)/packages/coding-agent/examples/extensions/subagent/index.ts" ~/.humain-terminal/agent/extensions/subagent/index.ts
|
|
40
|
-
ln -sf "$(pwd)/packages/coding-agent/examples/extensions/subagent/agents.ts" ~/.humain-terminal/agent/extensions/subagent/agents.ts
|
|
41
|
-
|
|
42
|
-
# Symlink agents
|
|
42
|
+
# User-scoped agents (default discovery scope)
|
|
43
43
|
mkdir -p ~/.humain-terminal/agent/agents
|
|
44
|
-
|
|
45
|
-
ln -sf "$(pwd)/$f" ~/.humain-terminal/agent/agents/$(basename "$f")
|
|
46
|
-
done
|
|
44
|
+
cp packages/coding-agent/examples/extensions/subagent/agents/*.md ~/.humain-terminal/agent/agents/
|
|
47
45
|
|
|
48
|
-
#
|
|
46
|
+
# Optional workflow prompt presets
|
|
49
47
|
mkdir -p ~/.humain-terminal/agent/prompts
|
|
50
|
-
|
|
51
|
-
ln -sf "$(pwd)/$f" ~/.humain-terminal/agent/prompts/$(basename "$f")
|
|
52
|
-
done
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
## Security Model
|
|
56
|
-
|
|
57
|
-
This tool executes a separate `pi` subprocess with a delegated system prompt and tool/model configuration.
|
|
58
|
-
|
|
59
|
-
**Project-local agents** (`.humain-terminal/agents/*.md`) are repo-controlled prompts that can instruct the model to read files, run bash commands, etc.
|
|
60
|
-
|
|
61
|
-
**Default behavior:** Only loads **user-level agents** from `~/.humain-terminal/agent/agents`.
|
|
62
|
-
|
|
63
|
-
To enable project-local agents, pass `agentScope: "both"` (or `"project"`). Only do this for repositories you trust.
|
|
64
|
-
|
|
65
|
-
When running interactively, the tool prompts for confirmation before running project-local agents. Set `confirmProjectAgents: false` to disable.
|
|
66
|
-
|
|
67
|
-
## Usage
|
|
68
|
-
|
|
69
|
-
### Single agent
|
|
70
|
-
```
|
|
71
|
-
Use scout to find all authentication code
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### Parallel execution
|
|
75
|
-
```
|
|
76
|
-
Run 2 scouts in parallel: one to find models, one to find providers
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### Chained workflow
|
|
80
|
-
```
|
|
81
|
-
Use a chain: first have scout find the read tool, then have planner suggest improvements
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
### Workflow prompts
|
|
85
|
-
```
|
|
86
|
-
/implement add Redis caching to the session store
|
|
87
|
-
/scout-and-plan refactor auth to support OAuth
|
|
88
|
-
/implement-and-review add input validation to API endpoints
|
|
48
|
+
cp packages/coding-agent/examples/extensions/subagent/prompts/*.md ~/.humain-terminal/agent/prompts/
|
|
89
49
|
```
|
|
90
50
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
| Mode | Parameter | Description |
|
|
94
|
-
|------|-----------|-------------|
|
|
95
|
-
| Single | `{ agent, task }` | One agent, one task |
|
|
96
|
-
| Parallel | `{ tasks: [...] }` | Multiple agents run concurrently (max 8, 4 concurrent) |
|
|
97
|
-
| Chain | `{ chain: [...] }` | Sequential with `{previous}` placeholder |
|
|
98
|
-
|
|
99
|
-
## Output Display
|
|
100
|
-
|
|
101
|
-
**Collapsed view** (default):
|
|
102
|
-
- Status icon (✓/✗/⏳) and agent name
|
|
103
|
-
- Last 5-10 items (tool calls and text)
|
|
104
|
-
- Usage stats: `3 turns ↑input ↓output RcacheRead WcacheWrite $cost ctx:contextTokens model`
|
|
105
|
-
|
|
106
|
-
**Expanded view** (Ctrl+O):
|
|
107
|
-
- Full task text
|
|
108
|
-
- All tool calls with formatted arguments
|
|
109
|
-
- Final output rendered as Markdown
|
|
110
|
-
- Per-task usage (for chain/parallel)
|
|
111
|
-
|
|
112
|
-
**Parallel mode streaming**:
|
|
113
|
-
- Shows all tasks with live status (⏳ running, ✓ done, ✗ failed)
|
|
114
|
-
- Updates as each task makes progress
|
|
115
|
-
- Shows "2/3 done, 1 running" status
|
|
116
|
-
- Returns each completed task's final output to the parent model, capped at 50 KB per task
|
|
117
|
-
- Returns failure diagnostics from stderr/error messages when a child exits before producing output
|
|
118
|
-
|
|
119
|
-
**Tool call formatting** (mimics built-in tools):
|
|
120
|
-
- `$ command` for bash
|
|
121
|
-
- `read ~/path:1-10` for read
|
|
122
|
-
- `grep /pattern/ in ~/path` for grep
|
|
123
|
-
- etc.
|
|
124
|
-
|
|
125
|
-
## Agent Definitions
|
|
126
|
-
|
|
127
|
-
Agents are markdown files with YAML frontmatter:
|
|
128
|
-
|
|
129
|
-
```markdown
|
|
130
|
-
---
|
|
131
|
-
name: my-agent
|
|
132
|
-
description: What this agent does
|
|
133
|
-
tools: read, grep, find, ls
|
|
134
|
-
model: claude-haiku-4-5
|
|
135
|
-
---
|
|
136
|
-
|
|
137
|
-
System prompt for the agent goes here.
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
**Locations:**
|
|
141
|
-
- `~/.humain-terminal/agent/agents/*.md` - User-level (always loaded)
|
|
142
|
-
- `.humain-terminal/agents/*.md` - Project-level (only with `agentScope: "project"` or `"both"`)
|
|
143
|
-
|
|
144
|
-
Project agents override user agents with the same name when `agentScope: "both"`.
|
|
145
|
-
|
|
146
|
-
## Sample Agents
|
|
147
|
-
|
|
148
|
-
| Agent | Purpose | Model | Tools |
|
|
149
|
-
|-------|---------|-------|-------|
|
|
150
|
-
| `scout` | Fast codebase recon | Haiku | read, grep, find, ls, bash |
|
|
151
|
-
| `planner` | Implementation plans | Sonnet | read, grep, find, ls |
|
|
152
|
-
| `reviewer` | Code review | Sonnet | read, grep, find, ls, bash |
|
|
153
|
-
| `worker` | General-purpose | Sonnet | (all default) |
|
|
154
|
-
|
|
155
|
-
## Workflow Prompts
|
|
156
|
-
|
|
157
|
-
| Prompt | Flow |
|
|
158
|
-
|--------|------|
|
|
159
|
-
| `/implement <query>` | scout → planner → worker |
|
|
160
|
-
| `/scout-and-plan <query>` | scout → planner |
|
|
161
|
-
| `/implement-and-review <query>` | worker → reviewer → worker |
|
|
162
|
-
|
|
163
|
-
## Error Handling
|
|
164
|
-
|
|
165
|
-
- **Exit code != 0**: Tool returns error with stderr/output
|
|
166
|
-
- **stopReason "error"**: LLM error propagated with error message
|
|
167
|
-
- **stopReason "aborted"**: User abort (Ctrl+C) kills subprocess, throws error
|
|
168
|
-
- **Chain mode**: Stops at first failing step, reports which step failed
|
|
169
|
-
|
|
170
|
-
## Limitations
|
|
171
|
-
|
|
172
|
-
- Output truncated to last 10 items in collapsed view (expand to see all)
|
|
173
|
-
- Parallel model-visible output is capped at 50 KB per task; full results remain in tool details
|
|
174
|
-
- Agents discovered fresh on each invocation (allows editing mid-session)
|
|
175
|
-
- Parallel mode limited to 8 tasks, 4 concurrent
|
|
51
|
+
Project-local agents live in `.humain-terminal/agents/` inside a repository and
|
|
52
|
+
require `agentScope: "both"` (or `"project"`) plus an interactive confirmation.
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@humain/terminal",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4-beta.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@humain/terminal",
|
|
9
|
-
"version": "0.0.
|
|
9
|
+
"version": "0.0.4-beta.1",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
|
+
"@aws-sdk/client-bedrock-runtime": "3.1048.0",
|
|
12
13
|
"@earendil-works/pi-agent-core": "0.84.1",
|
|
13
14
|
"@earendil-works/pi-ai": "0.84.1",
|
|
14
15
|
"@earendil-works/pi-client": "0.84.1",
|
|
@@ -20,6 +21,7 @@
|
|
|
20
21
|
"@mozilla/readability": "0.6.0",
|
|
21
22
|
"@napi-rs/keyring": "1.3.0",
|
|
22
23
|
"@silvia-odwyer/photon-node": "0.3.4",
|
|
24
|
+
"@smithy/node-http-handler": "4.7.3",
|
|
23
25
|
"chalk": "5.6.2",
|
|
24
26
|
"ajv": "8.20.0",
|
|
25
27
|
"ajv-formats": "3.0.1",
|
|
@@ -30,10 +32,13 @@
|
|
|
30
32
|
"highlight.js": "10.7.3",
|
|
31
33
|
"hosted-git-info": "9.0.3",
|
|
32
34
|
"ignore": "7.0.5",
|
|
35
|
+
"http-proxy-agent": "7.0.2",
|
|
36
|
+
"https-proxy-agent": "7.0.6",
|
|
33
37
|
"jiti": "2.7.0",
|
|
34
38
|
"linkedom": "0.16.11",
|
|
35
39
|
"minimatch": "10.2.5",
|
|
36
40
|
"p-limit": "6.1.0",
|
|
41
|
+
"partial-json": "0.1.7",
|
|
37
42
|
"open": "10.2.0",
|
|
38
43
|
"promise.try": "2.0.1",
|
|
39
44
|
"proper-lockfile": "4.1.2",
|