@pi-unipi/web-api 2.14.1 → 2.14.2
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 +2 -2
- package/src/engine/errors.ts +30 -0
- package/src/engine/extract.ts +18 -15
- package/src/providers/wigolo-client.ts +3 -1
- package/src/tools.ts +6 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/web-api",
|
|
3
|
-
"version": "2.14.
|
|
3
|
+
"version": "2.14.2",
|
|
4
4
|
"description": "Web search, read, and summarize tools with provider-based backend selection for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"README.md"
|
|
32
32
|
],
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@pi-unipi/core": "2.14.
|
|
34
|
+
"@pi-unipi/core": "2.14.2",
|
|
35
35
|
"defuddle": "^0.18.1",
|
|
36
36
|
"linkedom": "^0.18.12",
|
|
37
37
|
"wreq-js": "^2.3.0"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @unipi/web-api — error serialization
|
|
3
|
+
*
|
|
4
|
+
* Engines and providers throw a mix of Error instances and plain objects
|
|
5
|
+
* (FetchError-shaped payloads, wreq-js native errors, provider API bodies).
|
|
6
|
+
* `String()` on a plain object yields "[object Object]", which masked the
|
|
7
|
+
* real diagnosis behind "Read failed: [object Object]". Everything that
|
|
8
|
+
* surfaces an error to the agent must go through describeError().
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Best-effort human-readable message for any thrown value. */
|
|
12
|
+
export function describeError(error: unknown): string {
|
|
13
|
+
if (error instanceof Error) return error.message || error.toString();
|
|
14
|
+
if (typeof error === "string") return error;
|
|
15
|
+
if (error && typeof error === "object") {
|
|
16
|
+
const obj = error as Record<string, unknown>;
|
|
17
|
+
for (const key of ["message", "error", "reason", "detail"]) {
|
|
18
|
+
const v = obj[key];
|
|
19
|
+
if (typeof v === "string" && v.trim()) return v;
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
return JSON.stringify(error) ?? String(error);
|
|
23
|
+
} catch {
|
|
24
|
+
// circular or otherwise unserializable — describe by shape
|
|
25
|
+
const keys = Object.getOwnPropertyNames(obj).join(",");
|
|
26
|
+
return keys ? `[${keys}]` : String(error);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return String(error);
|
|
30
|
+
}
|
package/src/engine/extract.ts
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
} from "./constants.js";
|
|
26
26
|
import { resolveBrowserProfile, resolveOSProfile } from "./profiles.js";
|
|
27
27
|
import { getWreq, getDefuddle } from "./dependencies.js";
|
|
28
|
+
import { describeError } from "./errors.js";
|
|
28
29
|
import { parseHTML, extractTextContent, elementToMarkdown } from "./dom.js";
|
|
29
30
|
import { truncateContent, formatContent } from "./format.js";
|
|
30
31
|
|
|
@@ -68,21 +69,22 @@ function validateUrl(url: string): URL {
|
|
|
68
69
|
|
|
69
70
|
/**
|
|
70
71
|
* Create a FetchError object.
|
|
72
|
+
*
|
|
73
|
+
* Returns a REAL Error instance carrying the FetchError fields (.error,
|
|
74
|
+
* .code, .phase, .retryable) so boundary catches surface `message` instead
|
|
75
|
+
* of serializing a plain object to "[object Object]".
|
|
71
76
|
*/
|
|
72
|
-
function createError(
|
|
77
|
+
export function createError(
|
|
73
78
|
code: FetchError["code"],
|
|
74
79
|
phase: FetchError["phase"],
|
|
75
80
|
message: string,
|
|
76
81
|
retryable: boolean,
|
|
77
82
|
extra: Partial<FetchError> = {}
|
|
78
83
|
): FetchError {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
retryable,
|
|
84
|
-
...extra,
|
|
85
|
-
};
|
|
84
|
+
const err = new Error(message) as Error & FetchError;
|
|
85
|
+
err.name = "FetchError";
|
|
86
|
+
Object.assign(err, { error: message, code, phase, retryable, ...extra });
|
|
87
|
+
return err;
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
/**
|
|
@@ -401,25 +403,26 @@ export async function defuddleFetch(
|
|
|
401
403
|
throw error;
|
|
402
404
|
}
|
|
403
405
|
|
|
404
|
-
|
|
406
|
+
// Classify error — describeError() is safe on plain objects from the
|
|
407
|
+
// native wreq binding that carry no .message of their own.
|
|
408
|
+
const message = describeError(error);
|
|
405
409
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
throw createError("timeout", "waiting", err.message, true, {
|
|
410
|
+
if (message.includes("timeout")) {
|
|
411
|
+
throw createError("timeout", "waiting", message, true, {
|
|
409
412
|
url,
|
|
410
413
|
finalUrl,
|
|
411
414
|
timeoutMs,
|
|
412
415
|
});
|
|
413
416
|
}
|
|
414
417
|
|
|
415
|
-
if (
|
|
416
|
-
throw createError("network_error", "connecting",
|
|
418
|
+
if (message.includes("network") || message.includes("ECONNREFUSED")) {
|
|
419
|
+
throw createError("network_error", "connecting", message, true, {
|
|
417
420
|
url,
|
|
418
421
|
finalUrl,
|
|
419
422
|
});
|
|
420
423
|
}
|
|
421
424
|
|
|
422
|
-
throw createError("unexpected_response", "loading",
|
|
425
|
+
throw createError("unexpected_response", "loading", message, false, {
|
|
423
426
|
url,
|
|
424
427
|
finalUrl,
|
|
425
428
|
});
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
* module-level singleton, closed on session shutdown.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
import { describeError } from "../engine/errors.js";
|
|
19
|
+
|
|
18
20
|
/** Minimal structural types — avoids a type-level dependency on the AGPL SDK. */
|
|
19
21
|
interface WigoloSearchResponse {
|
|
20
22
|
results?: unknown[];
|
|
@@ -112,7 +114,7 @@ export async function getWigoloClient(): Promise<WigoloClientLike> {
|
|
|
112
114
|
try {
|
|
113
115
|
return await sdk.createLocalClient();
|
|
114
116
|
} catch (error) {
|
|
115
|
-
const detail =
|
|
117
|
+
const detail = describeError(error);
|
|
116
118
|
throw new WigoloUnavailableError(`${NOT_RUNNING_MESSAGE}\n→ Cause: ${detail}`);
|
|
117
119
|
}
|
|
118
120
|
})();
|
package/src/tools.ts
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
} from "./engine/extract.js";
|
|
29
29
|
import type { FetchOptions, FetchResult, BatchFetchResult } from "./engine/types.js";
|
|
30
30
|
import { formatSingleResult, formatBatchResult } from "./engine/format.js";
|
|
31
|
+
import { describeError } from "./engine/errors.js";
|
|
31
32
|
|
|
32
33
|
/** Tool names */
|
|
33
34
|
export const WEB_TOOLS = {
|
|
@@ -124,7 +125,7 @@ export async function withProviderFallthrough<T>(
|
|
|
124
125
|
try {
|
|
125
126
|
return await attempt(provider);
|
|
126
127
|
} catch (error) {
|
|
127
|
-
const message =
|
|
128
|
+
const message = describeError(error);
|
|
128
129
|
failures.push(`${provider.name}: ${message}`);
|
|
129
130
|
}
|
|
130
131
|
}
|
|
@@ -339,7 +340,7 @@ export function registerWebTools(pi: ExtensionAPI): void {
|
|
|
339
340
|
};
|
|
340
341
|
} catch (error) {
|
|
341
342
|
const message =
|
|
342
|
-
|
|
343
|
+
describeError(error);
|
|
343
344
|
return {
|
|
344
345
|
content: [{ type: "text", text: `Search failed: ${message}` }],
|
|
345
346
|
isError: true,
|
|
@@ -503,7 +504,7 @@ export function registerWebTools(pi: ExtensionAPI): void {
|
|
|
503
504
|
return {
|
|
504
505
|
url,
|
|
505
506
|
status: "error",
|
|
506
|
-
error:
|
|
507
|
+
error: describeError(error),
|
|
507
508
|
};
|
|
508
509
|
}
|
|
509
510
|
})
|
|
@@ -560,7 +561,7 @@ export function registerWebTools(pi: ExtensionAPI): void {
|
|
|
560
561
|
};
|
|
561
562
|
} catch (error) {
|
|
562
563
|
const message =
|
|
563
|
-
|
|
564
|
+
describeError(error);
|
|
564
565
|
return {
|
|
565
566
|
content: [{ type: "text", text: `Read failed: ${message}` }],
|
|
566
567
|
isError: true,
|
|
@@ -623,7 +624,7 @@ export function registerWebTools(pi: ExtensionAPI): void {
|
|
|
623
624
|
};
|
|
624
625
|
} catch (error) {
|
|
625
626
|
const message =
|
|
626
|
-
|
|
627
|
+
describeError(error);
|
|
627
628
|
return {
|
|
628
629
|
content: [{ type: "text", text: `Summarize failed: ${message}` }],
|
|
629
630
|
isError: true,
|