@jmcombs/pi-context7 2.0.0 → 2.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 (2) hide show
  1. package/index.ts +78 -21
  2. package/package.json +2 -2
package/index.ts CHANGED
@@ -16,6 +16,7 @@
16
16
  * 3. `/context7_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";
@@ -33,22 +34,6 @@ interface InfoSnippet {
33
34
  content?: string;
34
35
  }
35
36
 
36
- interface Context7SearchResult {
37
- id: string;
38
- title: string;
39
- [key: string]: unknown;
40
- }
41
-
42
- interface Context7SearchResponse {
43
- results?: Context7SearchResult[];
44
- }
45
-
46
- interface Context7DocsResponse {
47
- codeSnippets?: CodeSnippet[];
48
- infoSnippets?: InfoSnippet[];
49
- [key: string]: unknown;
50
- }
51
-
52
37
  // -- Tool parameter schemas
53
38
 
54
39
  const context7SearchSchema = Type.Object({
@@ -75,8 +60,57 @@ export type Context7GetDocsInput = Static<typeof context7GetDocsSchema>;
75
60
 
76
61
  // -- Helpers
77
62
 
78
- function formatDocs(data: Context7DocsResponse, query: string): string {
79
- const { codeSnippets = [], infoSnippets = [] } = data;
63
+ export function isJsonValue(value: unknown): value is JsonValue {
64
+ if (value === null) return true;
65
+ switch (typeof value) {
66
+ case "boolean":
67
+ case "number":
68
+ case "string":
69
+ return true;
70
+ case "object": {
71
+ if (Array.isArray(value)) return value.every(isJsonValue);
72
+ const nested: unknown[] = Object.values(value);
73
+ return nested.every(isJsonValue);
74
+ }
75
+ default:
76
+ return false;
77
+ }
78
+ }
79
+
80
+ function asJsonObject(value: JsonValue): JsonObject | undefined {
81
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined;
82
+ return value as JsonObject;
83
+ }
84
+
85
+ function formatDocs(data: JsonValue, query: string): string {
86
+ const obj = asJsonObject(data);
87
+ const codeSnippetsRaw = obj && Array.isArray(obj.codeSnippets) ? obj.codeSnippets : [];
88
+ const infoSnippetsRaw = obj && Array.isArray(obj.infoSnippets) ? obj.infoSnippets : [];
89
+ const codeSnippets: CodeSnippet[] = [];
90
+ for (const snippet of codeSnippetsRaw) {
91
+ const item = asJsonObject(snippet);
92
+ if (!item) continue;
93
+ const next: CodeSnippet = {};
94
+ if (typeof item.codeTitle === "string") next.codeTitle = item.codeTitle;
95
+ if (Array.isArray(item.codeList)) {
96
+ next.codeList = item.codeList.flatMap((entry) => {
97
+ const codeItem = asJsonObject(entry);
98
+ if (!codeItem) return [];
99
+ const language = typeof codeItem.language === "string" ? codeItem.language : undefined;
100
+ const code = typeof codeItem.code === "string" ? codeItem.code : undefined;
101
+ return [{ language, code }];
102
+ });
103
+ }
104
+ codeSnippets.push(next);
105
+ }
106
+ const infoSnippets: InfoSnippet[] = [];
107
+ for (const snippet of infoSnippetsRaw) {
108
+ const item = asJsonObject(snippet);
109
+ if (!item) continue;
110
+ const next: InfoSnippet = {};
111
+ if (typeof item.content === "string") next.content = item.content;
112
+ infoSnippets.push(next);
113
+ }
80
114
 
81
115
  if (codeSnippets.length === 0 && infoSnippets.length === 0) {
82
116
  return `No documentation snippets found for ${query}.`;
@@ -212,8 +246,24 @@ export default function (pi: ExtensionAPI): void {
212
246
  };
213
247
  }
214
248
 
215
- const data = (await response.json()) as Context7SearchResponse;
216
- const libs = data.results ?? [];
249
+ const parsed: unknown = await response.json();
250
+ if (!isJsonValue(parsed)) {
251
+ return {
252
+ content: [{ type: "text", text: "Context7 API returned invalid JSON." }],
253
+ details: { error: "invalid_json" },
254
+ };
255
+ }
256
+ const data = parsed;
257
+ const obj = asJsonObject(data);
258
+ const libsRaw = obj && Array.isArray(obj.results) ? obj.results : [];
259
+ const libs: { id: string; title: string }[] = [];
260
+ for (const lib of libsRaw) {
261
+ const item = asJsonObject(lib);
262
+ if (!item) continue;
263
+ if (typeof item.id === "string" && typeof item.title === "string") {
264
+ libs.push({ id: item.id, title: item.title });
265
+ }
266
+ }
217
267
 
218
268
  if (libs.length === 0) {
219
269
  return {
@@ -346,7 +396,14 @@ export default function (pi: ExtensionAPI): void {
346
396
  };
347
397
  }
348
398
 
349
- const data = (await response.json()) as Context7DocsResponse;
399
+ const parsed: unknown = await response.json();
400
+ if (!isJsonValue(parsed)) {
401
+ return {
402
+ content: [{ type: "text", text: "Context7 API returned invalid JSON." }],
403
+ details: { error: "invalid_json" },
404
+ };
405
+ }
406
+ const data = parsed;
350
407
  return {
351
408
  content: [{ type: "text", text: formatDocs(data, params.query) }],
352
409
  details: { libraryId: params.libraryId, query: params.query, raw: data },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jmcombs/pi-context7",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "private": false,
5
5
  "description": "Real-time documentation for the Pi coding agent via Context7.",
6
6
  "homepage": "https://github.com/jmcombs/pi-extensions/tree/main/packages/context7",
@@ -34,7 +34,7 @@
34
34
  "image": "https://raw.githubusercontent.com/jmcombs/pi-extensions/main/assets/context7/preview.png"
35
35
  },
36
36
  "engines": {
37
- "node": ">=22.0.0"
37
+ "node": ">=22.19.0"
38
38
  },
39
39
  "dependencies": {
40
40
  "@jmcombs/pi-1password": "^1.0.2 || ^2.0.0"