@jmcombs/pi-tavily-search 3.0.0 → 3.0.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/README.md +1 -1
- package/index.ts +68 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -163,7 +163,7 @@ export TAVILY_API_KEY="tvly-..."
|
|
|
163
163
|
## Requirements
|
|
164
164
|
|
|
165
165
|
- Pi `>= 0.80.8` (credentials via the `@jmcombs/pi-1password` API and `ExtensionAPI`)
|
|
166
|
-
- Node `>= 22.
|
|
166
|
+
- Node `>= 22.19.0`
|
|
167
167
|
- A Tavily API key
|
|
168
168
|
- Optional: the `op` (1Password) CLI for vault-backed onboarding and startup unlock
|
|
169
169
|
|
package/index.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* 3. `/tavily_setup` runs the same onboarding flow on demand.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
+
import type { JsonObject, JsonValue } from "@earendil-works/pi-ai";
|
|
19
20
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
20
21
|
import { onboardSecret, resolveSecret } from "@jmcombs/pi-1password";
|
|
21
22
|
import { type Static, Type } from "typebox";
|
|
@@ -55,6 +56,62 @@ interface TavilySearchResponse {
|
|
|
55
56
|
|
|
56
57
|
// ── Helpers ────────────────────────────────────────────────────────────
|
|
57
58
|
|
|
59
|
+
export function isJsonValue(value: unknown): value is JsonValue {
|
|
60
|
+
if (value === null) return true;
|
|
61
|
+
switch (typeof value) {
|
|
62
|
+
case "boolean":
|
|
63
|
+
case "number":
|
|
64
|
+
case "string":
|
|
65
|
+
return true;
|
|
66
|
+
case "object": {
|
|
67
|
+
if (Array.isArray(value)) return value.every(isJsonValue);
|
|
68
|
+
const nested: unknown[] = Object.values(value);
|
|
69
|
+
return nested.every(isJsonValue);
|
|
70
|
+
}
|
|
71
|
+
default:
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function asJsonObject(value: JsonValue): JsonObject | undefined {
|
|
77
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined;
|
|
78
|
+
return value as JsonObject;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function toTavilySearchResponse(data: JsonValue): TavilySearchResponse {
|
|
82
|
+
const obj = asJsonObject(data);
|
|
83
|
+
if (!obj) return {};
|
|
84
|
+
const out: TavilySearchResponse = {};
|
|
85
|
+
if (typeof obj.query === "string") out.query = obj.query;
|
|
86
|
+
if (typeof obj.answer === "string") out.answer = obj.answer;
|
|
87
|
+
if (Array.isArray(obj.results)) {
|
|
88
|
+
const results: TavilySearchResult[] = [];
|
|
89
|
+
for (const item of obj.results) {
|
|
90
|
+
const entry = asJsonObject(item);
|
|
91
|
+
if (!entry) continue;
|
|
92
|
+
if (
|
|
93
|
+
typeof entry.title !== "string" ||
|
|
94
|
+
typeof entry.url !== "string" ||
|
|
95
|
+
typeof entry.content !== "string"
|
|
96
|
+
) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const result: TavilySearchResult = {
|
|
100
|
+
title: entry.title,
|
|
101
|
+
url: entry.url,
|
|
102
|
+
content: entry.content,
|
|
103
|
+
};
|
|
104
|
+
if (typeof entry.score === "number") result.score = entry.score;
|
|
105
|
+
if (typeof entry.raw_content === "string" || entry.raw_content === null) {
|
|
106
|
+
result.raw_content = entry.raw_content;
|
|
107
|
+
}
|
|
108
|
+
results.push(result);
|
|
109
|
+
}
|
|
110
|
+
out.results = results;
|
|
111
|
+
}
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
|
|
58
115
|
function formatResults(data: TavilySearchResponse, query: string): string {
|
|
59
116
|
const results = data.results ?? [];
|
|
60
117
|
if (results.length === 0) {
|
|
@@ -132,9 +189,18 @@ export default function (pi: ExtensionAPI): void {
|
|
|
132
189
|
};
|
|
133
190
|
}
|
|
134
191
|
|
|
135
|
-
const
|
|
192
|
+
const parsed: unknown = await response.json();
|
|
193
|
+
if (!isJsonValue(parsed)) {
|
|
194
|
+
return {
|
|
195
|
+
content: [{ type: "text", text: "Tavily API returned invalid JSON." }],
|
|
196
|
+
details: { error: "invalid_json" },
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
const data = parsed;
|
|
136
200
|
return {
|
|
137
|
-
content: [
|
|
201
|
+
content: [
|
|
202
|
+
{ type: "text", text: formatResults(toTavilySearchResponse(data), params.query) },
|
|
203
|
+
],
|
|
138
204
|
details: { raw: data },
|
|
139
205
|
};
|
|
140
206
|
} catch (error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jmcombs/pi-tavily-search",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Pi extension that performs real-time web search via the Tavily API.",
|
|
5
5
|
"homepage": "https://github.com/jmcombs/pi-extensions/tree/main/packages/tavily-search",
|
|
6
6
|
"repository": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"image": "https://raw.githubusercontent.com/jmcombs/pi-extensions/main/assets/tavily-search/preview.png"
|
|
37
37
|
},
|
|
38
38
|
"engines": {
|
|
39
|
-
"node": ">=22.
|
|
39
|
+
"node": ">=22.19.0"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@jmcombs/pi-1password": "^1.0.2 || ^2.0.0"
|