@trim21/personal-pi-extensions 0.0.358 → 0.0.359
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/package.json +1 -1
- package/src/web/fetch.ts +21 -6
- package/src/web/index.ts +4 -3
package/package.json
CHANGED
package/src/web/fetch.ts
CHANGED
|
@@ -124,7 +124,8 @@ export async function fetchPage(url: string, signal?: AbortSignal): Promise<Fetc
|
|
|
124
124
|
throw new Error(`HTTP ${response.status} ${response.statusText}`);
|
|
125
125
|
}
|
|
126
126
|
const contentType = response.headers.get("content-type") ?? "";
|
|
127
|
-
|
|
127
|
+
const category = classifyContentType(contentType);
|
|
128
|
+
if (category === null) {
|
|
128
129
|
throw new Error(`不支持的内容类型: ${contentType || "unknown"}`);
|
|
129
130
|
}
|
|
130
131
|
|
|
@@ -133,22 +134,36 @@ export async function fetchPage(url: string, signal?: AbortSignal): Promise<Fetc
|
|
|
133
134
|
throw new Error(`页面过大 (${declaredLength} bytes),上限 ${MAX_BYTES}`);
|
|
134
135
|
}
|
|
135
136
|
|
|
136
|
-
let
|
|
137
|
+
let body = "";
|
|
137
138
|
if (response.body) {
|
|
138
139
|
const reader = response.body.getReader();
|
|
139
140
|
const decoder = new TextDecoder();
|
|
140
141
|
for (;;) {
|
|
141
142
|
const chunk = (await reader.read()) as { done: boolean; value: Uint8Array };
|
|
142
143
|
if (chunk.done) break;
|
|
143
|
-
|
|
144
|
-
if (Buffer.byteLength(
|
|
144
|
+
body += decoder.decode(chunk.value, { stream: true });
|
|
145
|
+
if (Buffer.byteLength(body, "utf8") > MAX_BYTES) {
|
|
145
146
|
throw new Error(`页面过大,上限 ${MAX_BYTES} bytes`);
|
|
146
147
|
}
|
|
147
148
|
}
|
|
148
|
-
|
|
149
|
+
body += decoder.decode();
|
|
149
150
|
}
|
|
150
151
|
|
|
151
|
-
|
|
152
|
+
if (category === "html") {
|
|
153
|
+
return extractMarkdown(body, response.url);
|
|
154
|
+
}
|
|
155
|
+
// JSON / XML / text/*:原样返回
|
|
156
|
+
return { url: response.url, title: response.url, markdown: body.trim() };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** 按 mime 主体分类响应;html 走 readability,其余文本类原样返回 */
|
|
160
|
+
function classifyContentType(contentType: string): "html" | "text" | null {
|
|
161
|
+
const mime = contentType.split(";", 1)[0]?.trim().toLowerCase() ?? "";
|
|
162
|
+
if (mime === "text/html" || mime === "application/xhtml+xml") return "html";
|
|
163
|
+
if (mime.startsWith("text/")) return "text";
|
|
164
|
+
if (mime === "application/json" || mime.endsWith("+json")) return "text";
|
|
165
|
+
if (mime === "application/xml" || mime.endsWith("+xml")) return "text";
|
|
166
|
+
return null;
|
|
152
167
|
}
|
|
153
168
|
|
|
154
169
|
/** 提取用到的 document 最小接口(linkedom 类型是 any,显式标注避免 unsafe) */
|
package/src/web/index.ts
CHANGED
|
@@ -140,9 +140,10 @@ export default function webTools(pi: ExtensionAPI) {
|
|
|
140
140
|
name: "web_fetch",
|
|
141
141
|
label: "Web Fetch",
|
|
142
142
|
description:
|
|
143
|
-
"Fetch a URL and
|
|
144
|
-
"
|
|
145
|
-
|
|
143
|
+
"Fetch a URL and return its content as markdown (HTML pages) or raw text " +
|
|
144
|
+
"(JSON/XML/plain-text API responses). SSRF-protected: refuses private/internal " +
|
|
145
|
+
"addresses.",
|
|
146
|
+
promptSnippet: "Fetch a web page or API response",
|
|
146
147
|
parameters: Type.Object({
|
|
147
148
|
url: Type.String({ description: "The URL to fetch" }),
|
|
148
149
|
}),
|