@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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/index.ts +68 -2
  3. 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.0.0`
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 data = (await response.json()) as TavilySearchResponse;
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: [{ type: "text", text: formatResults(data, params.query) }],
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.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.0.0"
39
+ "node": ">=22.19.0"
40
40
  },
41
41
  "dependencies": {
42
42
  "@jmcombs/pi-1password": "^1.0.2 || ^2.0.0"