webfox 4.4.2 → 4.6.0
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/dist/{adapter-RL7JNB22.js → adapter-BQUN3ME2.js} +1 -1
- package/dist/{chunk-MNN3BEPJ.js → chunk-5TNPVBAZ.js} +7 -7
- package/dist/{chunk-VYMIR35R.js → chunk-KH54YH7D.js} +23 -2
- package/dist/{chunk-J3QO44LO.js → chunk-QXTTUDLI.js} +2 -2
- package/dist/cli.js +3 -3
- package/dist/config.schema.json +18 -1
- package/dist/document-schema.d.ts +114 -0
- package/dist/index.js +3 -3
- package/dist/pi.js +110 -27
- package/docs/provider.md +20 -0
- package/docs/reference.md +7 -0
- package/package.json +6 -6
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
optionSchema,
|
|
3
3
|
providers
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-KH54YH7D.js";
|
|
5
5
|
import {
|
|
6
6
|
CAPABILITIES
|
|
7
7
|
} from "./chunk-GPACLYNT.js";
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
// package.json
|
|
10
10
|
var package_default = {
|
|
11
11
|
name: "webfox",
|
|
12
|
-
version: "4.
|
|
12
|
+
version: "4.6.0",
|
|
13
13
|
description: "Search the web, extract pages, get grounded answers, and run deep research with the web CLI, a TypeScript library, and a pi extension. Bring your own providers and API keys.",
|
|
14
14
|
type: "module",
|
|
15
15
|
files: [
|
|
@@ -107,15 +107,14 @@ var package_default = {
|
|
|
107
107
|
openai: "^7.10.0",
|
|
108
108
|
"parallel-web": "^1.3.3",
|
|
109
109
|
picocolors: "^1.1.1",
|
|
110
|
-
typebox: "^1.3.6",
|
|
111
110
|
"valyu-js": "^2.10.1",
|
|
112
111
|
yaml: "^2.9.0"
|
|
113
112
|
},
|
|
114
113
|
devDependencies: {
|
|
115
114
|
"@biomejs/biome": "^2.5.4",
|
|
116
|
-
"@earendil-works/pi-ai": "^0.
|
|
117
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
118
|
-
"@earendil-works/pi-tui": "^0.
|
|
115
|
+
"@earendil-works/pi-ai": "^0.99.0",
|
|
116
|
+
"@earendil-works/pi-coding-agent": "^0.99.0",
|
|
117
|
+
"@earendil-works/pi-tui": "^0.99.0",
|
|
119
118
|
"@types/node": "^26.1.1",
|
|
120
119
|
esbuild: "^0.28.1",
|
|
121
120
|
typescript: "^7.0.2",
|
|
@@ -123,7 +122,8 @@ var package_default = {
|
|
|
123
122
|
},
|
|
124
123
|
peerDependencies: {
|
|
125
124
|
"@earendil-works/pi-coding-agent": "*",
|
|
126
|
-
"@earendil-works/pi-tui": "*"
|
|
125
|
+
"@earendil-works/pi-tui": "*",
|
|
126
|
+
typebox: "*"
|
|
127
127
|
},
|
|
128
128
|
peerDependenciesMeta: {
|
|
129
129
|
"@earendil-works/pi-coding-agent": {
|
|
@@ -637,6 +637,11 @@ var customProvider = defineProvider({
|
|
|
637
637
|
});
|
|
638
638
|
|
|
639
639
|
// src/providers/exa/definition.ts
|
|
640
|
+
var snapshotAsOf = {
|
|
641
|
+
type: "string",
|
|
642
|
+
minLength: 1,
|
|
643
|
+
description: "ISO datetime cutoff, such as '2026-07-01T00:00:00Z'. Return the newest stored page version at or before this instant; pages without an eligible version are omitted. Do not combine with maxAgeHours, livecrawlTimeout, or subpages."
|
|
644
|
+
};
|
|
640
645
|
var exaProvider = defineProvider({
|
|
641
646
|
id: "exa",
|
|
642
647
|
label: "Exa",
|
|
@@ -844,6 +849,7 @@ var exaProvider = defineProvider({
|
|
|
844
849
|
],
|
|
845
850
|
description: "Include AI-generated summary."
|
|
846
851
|
},
|
|
852
|
+
snapshotAsOf,
|
|
847
853
|
livecrawlTimeout: {
|
|
848
854
|
type: "integer",
|
|
849
855
|
minimum: 1,
|
|
@@ -897,7 +903,7 @@ var exaProvider = defineProvider({
|
|
|
897
903
|
}
|
|
898
904
|
},
|
|
899
905
|
additionalProperties: false,
|
|
900
|
-
description: "Content extraction and
|
|
906
|
+
description: "Content extraction, freshness, and historical snapshot controls. Put text, highlights, summary, snapshotAsOf, maxAgeHours, and livecrawlTimeout here, not directly in options."
|
|
901
907
|
}
|
|
902
908
|
},
|
|
903
909
|
description: "Exa search options."
|
|
@@ -908,12 +914,27 @@ var exaProvider = defineProvider({
|
|
|
908
914
|
"Set includeDomains or excludeDomains when the task names preferred sources, requires primary sources, or needs noisy domains filtered out.",
|
|
909
915
|
"For fresh Exa search content, use options.contents.maxAgeHours=0; for cache-only retrieval use -1. Never put maxAgeHours at the top level of options.",
|
|
910
916
|
"Do not send deprecated livecrawl, even alongside maxAgeHours. Remove livecrawl rather than moving it into contents. Exa ignores startCrawlDate/endCrawlDate; use startPublishedDate/endPublishedDate to filter publication dates, not cache freshness.",
|
|
917
|
+
"For historical page versions, set options.contents.snapshotAsOf to an ISO datetime. Use type auto, fast, or instant; omit category, maxAgeHours, livecrawl, livecrawlTimeout, and subpages. Snapshot bounds content, not ranking: Exa still discovers URLs using current retrieval signals. Snapshot is a research preview with limited access and lookback.",
|
|
911
918
|
"Use includeText/excludeText for short required or forbidden phrases in page text.",
|
|
912
919
|
"Request contents.text, contents.highlights, or contents.summary only when snippets are insufficient and richer source context is needed directly in search results."
|
|
913
920
|
],
|
|
914
921
|
retrySafe: true
|
|
915
922
|
},
|
|
916
923
|
contents: {
|
|
924
|
+
options: {
|
|
925
|
+
type: "object",
|
|
926
|
+
properties: {
|
|
927
|
+
snapshotAsOf,
|
|
928
|
+
text: {
|
|
929
|
+
type: "boolean",
|
|
930
|
+
description: "Include text content."
|
|
931
|
+
}
|
|
932
|
+
},
|
|
933
|
+
description: "Exa contents options."
|
|
934
|
+
},
|
|
935
|
+
promptGuidelines: [
|
|
936
|
+
"For historical page versions, set options.snapshotAsOf to an ISO datetime (not nested inside contents). Exa returns the newest stored version at or before that instant and omits pages without an eligible version. Snapshot is a research preview with limited access and lookback."
|
|
937
|
+
],
|
|
917
938
|
retrySafe: true
|
|
918
939
|
},
|
|
919
940
|
answer: {
|
|
@@ -1970,7 +1991,7 @@ var serpbaseProvider = defineProvider({
|
|
|
1970
1991
|
retrySafe: true
|
|
1971
1992
|
}
|
|
1972
1993
|
},
|
|
1973
|
-
load: async () => (await import("./adapter-
|
|
1994
|
+
load: async () => (await import("./adapter-BQUN3ME2.js")).adapter
|
|
1974
1995
|
});
|
|
1975
1996
|
|
|
1976
1997
|
// src/providers/serper/definition.ts
|
|
@@ -6,14 +6,14 @@ import {
|
|
|
6
6
|
} from "./chunk-WCTOYZVB.js";
|
|
7
7
|
import {
|
|
8
8
|
configurationSchema
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-5TNPVBAZ.js";
|
|
10
10
|
import {
|
|
11
11
|
effectiveOptions,
|
|
12
12
|
optionSchema,
|
|
13
13
|
providers,
|
|
14
14
|
selectProvider,
|
|
15
15
|
validateConfiguredOptions
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-KH54YH7D.js";
|
|
17
17
|
import {
|
|
18
18
|
cancelProcessGroup,
|
|
19
19
|
killProcessGroup
|
package/dist/cli.js
CHANGED
|
@@ -11,14 +11,14 @@ import {
|
|
|
11
11
|
redactConfig,
|
|
12
12
|
resolveConfigPath,
|
|
13
13
|
setCapabilityDefault
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-QXTTUDLI.js";
|
|
15
15
|
import "./chunk-WCTOYZVB.js";
|
|
16
16
|
import {
|
|
17
17
|
PACKAGE_VERSION
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-5TNPVBAZ.js";
|
|
19
19
|
import {
|
|
20
20
|
validateConfiguredOptions
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-KH54YH7D.js";
|
|
22
22
|
import "./chunk-ZVTVERW5.js";
|
|
23
23
|
import {
|
|
24
24
|
CAPABILITIES,
|
package/dist/config.schema.json
CHANGED
|
@@ -1388,6 +1388,11 @@
|
|
|
1388
1388
|
],
|
|
1389
1389
|
"description": "Include AI-generated summary."
|
|
1390
1390
|
},
|
|
1391
|
+
"snapshotAsOf": {
|
|
1392
|
+
"type": "string",
|
|
1393
|
+
"minLength": 1,
|
|
1394
|
+
"description": "ISO datetime cutoff, such as '2026-07-01T00:00:00Z'. Return the newest stored page version at or before this instant; pages without an eligible version are omitted. Do not combine with maxAgeHours, livecrawlTimeout, or subpages."
|
|
1395
|
+
},
|
|
1391
1396
|
"livecrawlTimeout": {
|
|
1392
1397
|
"type": "integer",
|
|
1393
1398
|
"minimum": 1,
|
|
@@ -1441,7 +1446,7 @@
|
|
|
1441
1446
|
}
|
|
1442
1447
|
},
|
|
1443
1448
|
"additionalProperties": false,
|
|
1444
|
-
"description": "Content extraction and
|
|
1449
|
+
"description": "Content extraction, freshness, and historical snapshot controls. Put text, highlights, summary, snapshotAsOf, maxAgeHours, and livecrawlTimeout here, not directly in options."
|
|
1445
1450
|
}
|
|
1446
1451
|
},
|
|
1447
1452
|
"description": "Exa search options.",
|
|
@@ -1449,6 +1454,18 @@
|
|
|
1449
1454
|
},
|
|
1450
1455
|
"contents": {
|
|
1451
1456
|
"type": "object",
|
|
1457
|
+
"properties": {
|
|
1458
|
+
"snapshotAsOf": {
|
|
1459
|
+
"type": "string",
|
|
1460
|
+
"minLength": 1,
|
|
1461
|
+
"description": "ISO datetime cutoff, such as '2026-07-01T00:00:00Z'. Return the newest stored page version at or before this instant; pages without an eligible version are omitted. Do not combine with maxAgeHours, livecrawlTimeout, or subpages."
|
|
1462
|
+
},
|
|
1463
|
+
"text": {
|
|
1464
|
+
"type": "boolean",
|
|
1465
|
+
"description": "Include text content."
|
|
1466
|
+
}
|
|
1467
|
+
},
|
|
1468
|
+
"description": "Exa contents options.",
|
|
1452
1469
|
"additionalProperties": false
|
|
1453
1470
|
},
|
|
1454
1471
|
"answer": {
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Type, type Static } from "typebox";
|
|
2
|
+
import { type AnswerDocument, type ContentsDocument, type ResearchDocument, type SearchDocument } from "./domain.js";
|
|
3
|
+
/**
|
|
4
|
+
* JSON Schema of each capability's result document. This is the document the
|
|
5
|
+
* CLI prints with `--format json` and the library returns.
|
|
6
|
+
*/
|
|
7
|
+
export declare const DOCUMENT_SCHEMAS: {
|
|
8
|
+
readonly search: Type.TObject<{
|
|
9
|
+
schemaVersion: Type.TLiteral<1>;
|
|
10
|
+
capability: Type.TLiteral<"search">;
|
|
11
|
+
provider: Type.TEnum<["brave", "cloudflare", "custom", "exa", "firecrawl", "gemini", "linkup", "ollama", "openai", "parallel", "perplexity", "serpbase", "serper", "tavily", "valyu", "youcom"]>;
|
|
12
|
+
status: Type.TEnum<["ok", "partial"]>;
|
|
13
|
+
results: Type.TArray<Type.TUnion<[Type.TObject<{
|
|
14
|
+
input: Type.TString;
|
|
15
|
+
ok: Type.TLiteral<true>;
|
|
16
|
+
value: Type.TObject<{
|
|
17
|
+
results: Type.TArray<Type.TObject<{
|
|
18
|
+
title: Type.TString;
|
|
19
|
+
url: Type.TString;
|
|
20
|
+
snippet: Type.TString;
|
|
21
|
+
score: Type.TOptional<Type.TNumber>;
|
|
22
|
+
metadata: Type.TOptional<Type.TRecord<"^.*$", Type.TUnknown>>;
|
|
23
|
+
}>>;
|
|
24
|
+
}>;
|
|
25
|
+
}>, Type.TObject<{
|
|
26
|
+
input: Type.TString;
|
|
27
|
+
ok: Type.TLiteral<false>;
|
|
28
|
+
error: Type.TObject<{
|
|
29
|
+
code: Type.TEnum<["INVALID_CONFIG", "INVALID_INPUT", "PROVIDER_UNAVAILABLE", "PROVIDER_FAILURE", "PARTIAL_BATCH", "TIMEOUT", "CANCELLED"]>;
|
|
30
|
+
message: Type.TString;
|
|
31
|
+
retryable: Type.TOptional<Type.TBoolean>;
|
|
32
|
+
}>;
|
|
33
|
+
}>]>>;
|
|
34
|
+
}>;
|
|
35
|
+
readonly contents: Type.TObject<{
|
|
36
|
+
schemaVersion: Type.TLiteral<1>;
|
|
37
|
+
capability: Type.TLiteral<"contents">;
|
|
38
|
+
provider: Type.TEnum<["brave", "cloudflare", "custom", "exa", "firecrawl", "gemini", "linkup", "ollama", "openai", "parallel", "perplexity", "serpbase", "serper", "tavily", "valyu", "youcom"]>;
|
|
39
|
+
status: Type.TEnum<["ok", "partial"]>;
|
|
40
|
+
results: Type.TArray<Type.TUnion<[Type.TObject<{
|
|
41
|
+
input: Type.TString;
|
|
42
|
+
ok: Type.TLiteral<true>;
|
|
43
|
+
value: Type.TObject<{
|
|
44
|
+
url: Type.TString;
|
|
45
|
+
content: Type.TOptional<Type.TString>;
|
|
46
|
+
summary: Type.TOptional<Type.TUnknown>;
|
|
47
|
+
metadata: Type.TOptional<Type.TRecord<"^.*$", Type.TUnknown>>;
|
|
48
|
+
}>;
|
|
49
|
+
}>, Type.TObject<{
|
|
50
|
+
input: Type.TString;
|
|
51
|
+
ok: Type.TLiteral<false>;
|
|
52
|
+
error: Type.TObject<{
|
|
53
|
+
code: Type.TEnum<["INVALID_CONFIG", "INVALID_INPUT", "PROVIDER_UNAVAILABLE", "PROVIDER_FAILURE", "PARTIAL_BATCH", "TIMEOUT", "CANCELLED"]>;
|
|
54
|
+
message: Type.TString;
|
|
55
|
+
retryable: Type.TOptional<Type.TBoolean>;
|
|
56
|
+
}>;
|
|
57
|
+
}>]>>;
|
|
58
|
+
}>;
|
|
59
|
+
readonly answer: Type.TObject<{
|
|
60
|
+
schemaVersion: Type.TLiteral<1>;
|
|
61
|
+
capability: Type.TLiteral<"answer">;
|
|
62
|
+
provider: Type.TEnum<["brave", "cloudflare", "custom", "exa", "firecrawl", "gemini", "linkup", "ollama", "openai", "parallel", "perplexity", "serpbase", "serper", "tavily", "valyu", "youcom"]>;
|
|
63
|
+
status: Type.TEnum<["ok", "partial"]>;
|
|
64
|
+
results: Type.TArray<Type.TUnion<[Type.TObject<{
|
|
65
|
+
input: Type.TString;
|
|
66
|
+
ok: Type.TLiteral<true>;
|
|
67
|
+
value: Type.TObject<{
|
|
68
|
+
text: Type.TString;
|
|
69
|
+
itemCount: Type.TOptional<Type.TInteger>;
|
|
70
|
+
metadata: Type.TOptional<Type.TRecord<"^.*$", Type.TUnknown>>;
|
|
71
|
+
}>;
|
|
72
|
+
}>, Type.TObject<{
|
|
73
|
+
input: Type.TString;
|
|
74
|
+
ok: Type.TLiteral<false>;
|
|
75
|
+
error: Type.TObject<{
|
|
76
|
+
code: Type.TEnum<["INVALID_CONFIG", "INVALID_INPUT", "PROVIDER_UNAVAILABLE", "PROVIDER_FAILURE", "PARTIAL_BATCH", "TIMEOUT", "CANCELLED"]>;
|
|
77
|
+
message: Type.TString;
|
|
78
|
+
retryable: Type.TOptional<Type.TBoolean>;
|
|
79
|
+
}>;
|
|
80
|
+
}>]>>;
|
|
81
|
+
}>;
|
|
82
|
+
readonly research: Type.TObject<{
|
|
83
|
+
schemaVersion: Type.TLiteral<1>;
|
|
84
|
+
capability: Type.TLiteral<"research">;
|
|
85
|
+
provider: Type.TEnum<["brave", "cloudflare", "custom", "exa", "firecrawl", "gemini", "linkup", "ollama", "openai", "parallel", "perplexity", "serpbase", "serper", "tavily", "valyu", "youcom"]>;
|
|
86
|
+
status: Type.TEnum<["ok", "partial"]>;
|
|
87
|
+
results: Type.TArray<Type.TUnion<[Type.TObject<{
|
|
88
|
+
input: Type.TString;
|
|
89
|
+
ok: Type.TLiteral<true>;
|
|
90
|
+
value: Type.TObject<{
|
|
91
|
+
text: Type.TString;
|
|
92
|
+
itemCount: Type.TOptional<Type.TInteger>;
|
|
93
|
+
metadata: Type.TOptional<Type.TRecord<"^.*$", Type.TUnknown>>;
|
|
94
|
+
}>;
|
|
95
|
+
}>, Type.TObject<{
|
|
96
|
+
input: Type.TString;
|
|
97
|
+
ok: Type.TLiteral<false>;
|
|
98
|
+
error: Type.TObject<{
|
|
99
|
+
code: Type.TEnum<["INVALID_CONFIG", "INVALID_INPUT", "PROVIDER_UNAVAILABLE", "PROVIDER_FAILURE", "PARTIAL_BATCH", "TIMEOUT", "CANCELLED"]>;
|
|
100
|
+
message: Type.TString;
|
|
101
|
+
retryable: Type.TOptional<Type.TBoolean>;
|
|
102
|
+
}>;
|
|
103
|
+
}>]>>;
|
|
104
|
+
}>;
|
|
105
|
+
};
|
|
106
|
+
type Same<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false;
|
|
107
|
+
type Assert<T extends true> = T;
|
|
108
|
+
export type DocumentSchemaMatchesDomain = [
|
|
109
|
+
Assert<Same<Static<typeof DOCUMENT_SCHEMAS.search>, SearchDocument>>,
|
|
110
|
+
Assert<Same<Static<typeof DOCUMENT_SCHEMAS.contents>, ContentsDocument>>,
|
|
111
|
+
Assert<Same<Static<typeof DOCUMENT_SCHEMAS.answer>, AnswerDocument>>,
|
|
112
|
+
Assert<Same<Static<typeof DOCUMENT_SCHEMAS.research>, ResearchDocument>>
|
|
113
|
+
];
|
|
114
|
+
export {};
|
package/dist/index.js
CHANGED
|
@@ -6,14 +6,14 @@ import {
|
|
|
6
6
|
resolveConfigPath,
|
|
7
7
|
setCapabilityDefault,
|
|
8
8
|
validateConfig
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-QXTTUDLI.js";
|
|
10
10
|
import "./chunk-WCTOYZVB.js";
|
|
11
11
|
import {
|
|
12
12
|
CONFIG_SCHEMA_URL
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-5TNPVBAZ.js";
|
|
14
14
|
import {
|
|
15
15
|
validateConfiguredOptions
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-KH54YH7D.js";
|
|
17
17
|
import "./chunk-ZVTVERW5.js";
|
|
18
18
|
import {
|
|
19
19
|
CAPABILITIES,
|
package/dist/pi.js
CHANGED
|
@@ -4,13 +4,15 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
createWebfox,
|
|
6
6
|
resolveConfigPath
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-QXTTUDLI.js";
|
|
8
8
|
import "./chunk-WCTOYZVB.js";
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
9
|
+
import "./chunk-5TNPVBAZ.js";
|
|
10
|
+
import "./chunk-KH54YH7D.js";
|
|
11
11
|
import "./chunk-ZVTVERW5.js";
|
|
12
12
|
import {
|
|
13
|
-
CAPABILITIES
|
|
13
|
+
CAPABILITIES,
|
|
14
|
+
PROVIDER_IDS,
|
|
15
|
+
WEBFOX_ERROR_CODES
|
|
14
16
|
} from "./chunk-GPACLYNT.js";
|
|
15
17
|
import {
|
|
16
18
|
WebfoxError
|
|
@@ -24,7 +26,7 @@ import {
|
|
|
24
26
|
truncateHead,
|
|
25
27
|
withFileMutationQueue
|
|
26
28
|
} from "@earendil-works/pi-coding-agent";
|
|
27
|
-
import { Type } from "typebox";
|
|
29
|
+
import { Type as Type2 } from "typebox";
|
|
28
30
|
|
|
29
31
|
// src/pi-render.ts
|
|
30
32
|
import { stripVTControlCharacters as stripVTControlCharacters3 } from "node:util";
|
|
@@ -186,11 +188,11 @@ function link(url, theme) {
|
|
|
186
188
|
return label;
|
|
187
189
|
}
|
|
188
190
|
function renderSearchResult(details, fallback, theme) {
|
|
189
|
-
const
|
|
191
|
+
const document2 = isRecord2(details) && searchDocument(details.result) ? details.result : void 0;
|
|
190
192
|
return {
|
|
191
193
|
render(width) {
|
|
192
194
|
if (width <= 0) return [];
|
|
193
|
-
if (!
|
|
195
|
+
if (!document2) {
|
|
194
196
|
const text = plainText(fallback).split("\n").map((line) => {
|
|
195
197
|
const urlLine = /^(\s*)(https?:\/\/\S+)(\s*)$/.exec(line);
|
|
196
198
|
return urlLine ? urlLine[1] + link(urlLine[2], theme) + urlLine[3] : theme.fg("toolOutput", line);
|
|
@@ -209,9 +211,9 @@ function renderSearchResult(details, fallback, theme) {
|
|
|
209
211
|
)
|
|
210
212
|
);
|
|
211
213
|
};
|
|
212
|
-
|
|
214
|
+
document2.results.forEach((entry, index) => {
|
|
213
215
|
if (index) lines.push("");
|
|
214
|
-
if (
|
|
216
|
+
if (document2.results.length > 1) {
|
|
215
217
|
append(
|
|
216
218
|
theme.fg(
|
|
217
219
|
"mdHeading",
|
|
@@ -288,11 +290,7 @@ var WebCall = class {
|
|
|
288
290
|
const { args, theme, capability } = this;
|
|
289
291
|
const title = theme.fg("toolTitle", theme.bold(`web ${capability}`));
|
|
290
292
|
let text = title;
|
|
291
|
-
const parameters = callParameters(
|
|
292
|
-
capability,
|
|
293
|
-
args,
|
|
294
|
-
(key2) => theme.bold(key2)
|
|
295
|
-
);
|
|
293
|
+
const parameters = callParameters(capability, args);
|
|
296
294
|
if (parameters) text += theme.fg("dim", ` ${parameters}`);
|
|
297
295
|
if (this.expanded)
|
|
298
296
|
return visibleWidth2(text) <= width ? [text] : new Text2(text, 0, 0).render(width).map((line) => truncateLine(line, width));
|
|
@@ -308,8 +306,8 @@ var WebCall = class {
|
|
|
308
306
|
};
|
|
309
307
|
function renderWebResult(result, options, theme, isError, capability) {
|
|
310
308
|
const text = result.content.filter((part) => part.type === "text").map((part) => part.text ?? "").join("\n");
|
|
311
|
-
const
|
|
312
|
-
const isSearch = (capability ??
|
|
309
|
+
const metadata2 = result.details;
|
|
310
|
+
const isSearch = (capability ?? metadata2?.capability ?? metadata2?.result?.capability) === "search";
|
|
313
311
|
const body = () => isSearch ? renderSearchResult(result.details, text, theme) : new Markdown(text, 0, 0, getMarkdownTheme());
|
|
314
312
|
const rows = statusRows(result.details);
|
|
315
313
|
if (rows?.length) {
|
|
@@ -439,6 +437,85 @@ function configurationDiagnostic(error) {
|
|
|
439
437
|
].join("\n");
|
|
440
438
|
}
|
|
441
439
|
|
|
440
|
+
// src/document-schema.ts
|
|
441
|
+
import { Type } from "typebox";
|
|
442
|
+
var closed = { additionalProperties: false };
|
|
443
|
+
var metadata = Type.Record(Type.String(), Type.Unknown(), {
|
|
444
|
+
description: "Provider-specific fields."
|
|
445
|
+
});
|
|
446
|
+
var searchResult = Type.Object(
|
|
447
|
+
{
|
|
448
|
+
title: Type.String(),
|
|
449
|
+
url: Type.String(),
|
|
450
|
+
snippet: Type.String(),
|
|
451
|
+
score: Type.Optional(Type.Number()),
|
|
452
|
+
metadata: Type.Optional(metadata)
|
|
453
|
+
},
|
|
454
|
+
closed
|
|
455
|
+
);
|
|
456
|
+
var contentsAnswer = Type.Object(
|
|
457
|
+
{
|
|
458
|
+
url: Type.String({ description: "Final URL after redirects." }),
|
|
459
|
+
content: Type.Optional(Type.String()),
|
|
460
|
+
summary: Type.Optional(Type.Unknown()),
|
|
461
|
+
metadata: Type.Optional(metadata)
|
|
462
|
+
},
|
|
463
|
+
closed
|
|
464
|
+
);
|
|
465
|
+
var textAnswer = Type.Object(
|
|
466
|
+
{
|
|
467
|
+
text: Type.String(),
|
|
468
|
+
itemCount: Type.Optional(Type.Integer()),
|
|
469
|
+
metadata: Type.Optional(metadata)
|
|
470
|
+
},
|
|
471
|
+
closed
|
|
472
|
+
);
|
|
473
|
+
var serializedError = Type.Object(
|
|
474
|
+
{
|
|
475
|
+
code: Type.Enum(WEBFOX_ERROR_CODES),
|
|
476
|
+
message: Type.String(),
|
|
477
|
+
retryable: Type.Optional(Type.Boolean())
|
|
478
|
+
},
|
|
479
|
+
closed
|
|
480
|
+
);
|
|
481
|
+
function inputResult(value) {
|
|
482
|
+
const input = Type.String({
|
|
483
|
+
description: "The query, URL, or brief this entry answers."
|
|
484
|
+
});
|
|
485
|
+
return Type.Union([
|
|
486
|
+
Type.Object({ input, ok: Type.Literal(true), value }, closed),
|
|
487
|
+
Type.Object(
|
|
488
|
+
{ input, ok: Type.Literal(false), error: serializedError },
|
|
489
|
+
closed
|
|
490
|
+
)
|
|
491
|
+
]);
|
|
492
|
+
}
|
|
493
|
+
function document(capability, value) {
|
|
494
|
+
return Type.Object(
|
|
495
|
+
{
|
|
496
|
+
schemaVersion: Type.Literal(1),
|
|
497
|
+
capability: Type.Literal(capability),
|
|
498
|
+
provider: Type.Enum(PROVIDER_IDS),
|
|
499
|
+
status: Type.Enum(["ok", "partial"], {
|
|
500
|
+
description: "`partial` when at least one input failed."
|
|
501
|
+
}),
|
|
502
|
+
results: Type.Array(inputResult(value), {
|
|
503
|
+
description: "One entry per input, in input order."
|
|
504
|
+
})
|
|
505
|
+
},
|
|
506
|
+
closed
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
var DOCUMENT_SCHEMAS = {
|
|
510
|
+
search: document(
|
|
511
|
+
"search",
|
|
512
|
+
Type.Object({ results: Type.Array(searchResult) }, closed)
|
|
513
|
+
),
|
|
514
|
+
contents: document("contents", contentsAnswer),
|
|
515
|
+
answer: document("answer", textAnswer),
|
|
516
|
+
research: document("research", textAnswer)
|
|
517
|
+
};
|
|
518
|
+
|
|
442
519
|
// src/pi.ts
|
|
443
520
|
function webExtension(pi) {
|
|
444
521
|
const clients = /* @__PURE__ */ new Map();
|
|
@@ -479,18 +556,18 @@ function webExtension(pi) {
|
|
|
479
556
|
for (const inspection of selected) {
|
|
480
557
|
const capability = inspection.capability;
|
|
481
558
|
const provider = inspection.provider;
|
|
482
|
-
const fields = capability === "contents" ? { urls:
|
|
483
|
-
queries:
|
|
559
|
+
const fields = capability === "contents" ? { urls: Type2.Array(Type2.String({ minLength: 1 }), { minItems: 1 }) } : capability === "research" ? { input: Type2.String({ minLength: 1 }) } : {
|
|
560
|
+
queries: Type2.Array(Type2.String({ minLength: 1 }), {
|
|
484
561
|
minItems: 1,
|
|
485
562
|
maxItems: 10
|
|
486
563
|
}),
|
|
487
|
-
...capability === "search" ? { maxResults:
|
|
564
|
+
...capability === "search" ? { maxResults: Type2.Optional(Type2.Integer({ minimum: 1 })) } : {}
|
|
488
565
|
};
|
|
489
|
-
const parameters =
|
|
566
|
+
const parameters = Type2.Object(
|
|
490
567
|
{
|
|
491
568
|
...fields,
|
|
492
569
|
...inspection.optionSchema ? {
|
|
493
|
-
options:
|
|
570
|
+
options: Type2.Optional(
|
|
494
571
|
inspection.optionSchema
|
|
495
572
|
)
|
|
496
573
|
} : {}
|
|
@@ -507,6 +584,12 @@ function webExtension(pi) {
|
|
|
507
584
|
"Output is truncated to 2000 lines or 50 KiB; full results are saved to a file when truncated."
|
|
508
585
|
].join("\n"),
|
|
509
586
|
parameters,
|
|
587
|
+
// Let permission extensions and tool loadouts treat web access as a
|
|
588
|
+
// read-only call that reaches the open web.
|
|
589
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
590
|
+
// Programmatic callers such as codemode scripts receive the result
|
|
591
|
+
// document itself instead of parsing the rendered text.
|
|
592
|
+
outputSchema: DOCUMENT_SCHEMAS[capability],
|
|
510
593
|
prepareArguments(args) {
|
|
511
594
|
return prepareToolArguments(parameters, args);
|
|
512
595
|
},
|
|
@@ -554,6 +637,7 @@ function webExtension(pi) {
|
|
|
554
637
|
queries: params.queries,
|
|
555
638
|
maxResults: params.maxResults
|
|
556
639
|
}) : capability === "answer" ? await client.answer({ ...request, queries: params.queries }) : capability === "contents" ? await client.contents({ ...request, urls: params.urls }) : await client.research({ ...request, input: params.input });
|
|
640
|
+
const json = JSON.stringify(result);
|
|
557
641
|
const text = renderTextDocument(result);
|
|
558
642
|
const truncated = truncateHead(text);
|
|
559
643
|
let body = truncated.content;
|
|
@@ -565,7 +649,7 @@ function webExtension(pi) {
|
|
|
565
649
|
);
|
|
566
650
|
await withFileMutationQueue(
|
|
567
651
|
fullOutputPath,
|
|
568
|
-
() => writeFile(fullOutputPath,
|
|
652
|
+
() => writeFile(fullOutputPath, json, { mode: 384 })
|
|
569
653
|
);
|
|
570
654
|
body += `
|
|
571
655
|
|
|
@@ -573,8 +657,11 @@ Full results: ${fullOutputPath}`;
|
|
|
573
657
|
}
|
|
574
658
|
return {
|
|
575
659
|
content: [{ type: "text", text: body }],
|
|
660
|
+
// Pi keeps this out of the model's context and the session, so the
|
|
661
|
+
// text truncation above does not apply. Scripts get every result.
|
|
662
|
+
structuredContent: JSON.parse(json),
|
|
663
|
+
isError: result.status === "partial",
|
|
576
664
|
details: {
|
|
577
|
-
webProviderResult: true,
|
|
578
665
|
status: result.status,
|
|
579
666
|
webInputStatus: true,
|
|
580
667
|
capability,
|
|
@@ -588,10 +675,6 @@ Full results: ${fullOutputPath}`;
|
|
|
588
675
|
}
|
|
589
676
|
});
|
|
590
677
|
}
|
|
591
|
-
pi.on("tool_result", (event) => {
|
|
592
|
-
if (event.details && typeof event.details === "object" && "webProviderResult" in event.details && event.details.webProviderResult === true && "status" in event.details && event.details.status === "partial")
|
|
593
|
-
return { isError: true };
|
|
594
|
-
});
|
|
595
678
|
}
|
|
596
679
|
var descriptions = {
|
|
597
680
|
search: "Search up to ten queries and return titles, URLs, and snippets in input order.",
|
package/docs/provider.md
CHANGED
|
@@ -74,6 +74,26 @@ web search "Node.js release notes" --provider exa
|
|
|
74
74
|
web research "Compare databases for an analytics service" --provider exa --timeout 20m
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
+
Use [Exa Snapshot](https://exa.ai/docs/search/snapshot) to retrieve the newest
|
|
78
|
+
stored page version at or before an ISO datetime. For search, set
|
|
79
|
+
`options.contents.snapshotAsOf`; for page extraction, set `options.snapshotAsOf`.
|
|
80
|
+
Both options are also exposed to the model when Exa is selected in pi.
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
web search "Python release notes" --provider exa \
|
|
84
|
+
--options-json '{"contents":{"snapshotAsOf":"2026-07-01T00:00:00Z"}}'
|
|
85
|
+
web contents https://docs.python.org/3/whatsnew/ --provider exa \
|
|
86
|
+
--options-json '{"snapshotAsOf":"2026-07-01T00:00:00Z","text":true}'
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Snapshot search supports `auto`, `fast`, and `instant`, without `category`.
|
|
90
|
+
Do not combine snapshots with `maxAgeHours`, `livecrawl`, `livecrawlTimeout`, or
|
|
91
|
+
`subpages`, including saved defaults. Pages without an eligible snapshot are
|
|
92
|
+
omitted by Exa; page extraction reports those URLs as failures. The cutoff bounds
|
|
93
|
+
page content, not ranking: Exa still discovers candidate URLs using current
|
|
94
|
+
retrieval signals. Snapshot is a research preview with limited access and
|
|
95
|
+
lookback; consult Exa's documentation for current limits.
|
|
96
|
+
|
|
77
97
|
## Firecrawl
|
|
78
98
|
|
|
79
99
|
Supports search, page extraction, and page-scoped answers. Set `FIRECRAWL_API_KEY`
|
package/docs/reference.md
CHANGED
|
@@ -179,3 +179,10 @@ extension forwards cancellation and progress, marks partial results as errors,
|
|
|
179
179
|
and truncates tool output at 2,000 lines or 50 KiB, with full results saved to a
|
|
180
180
|
temporary file. It needs no globally installed `web` command and starts no
|
|
181
181
|
background research jobs.
|
|
182
|
+
|
|
183
|
+
Each tool declares the [JSON result document](./cli-experience.md#json-results)
|
|
184
|
+
as its output schema and returns that document as structured content. Scripts
|
|
185
|
+
that call the tools through Pi's codemode receive it as an object instead of
|
|
186
|
+
parsing text, including the completed inputs of a partial result. Structured
|
|
187
|
+
content is never truncated, and Pi neither sends it to the model nor stores it
|
|
188
|
+
in the session.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webfox",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.6.0",
|
|
4
4
|
"description": "Search the web, extract pages, get grounded answers, and run deep research with the web CLI, a TypeScript library, and a pi extension. Bring your own providers and API keys.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -98,15 +98,14 @@
|
|
|
98
98
|
"openai": "^7.10.0",
|
|
99
99
|
"parallel-web": "^1.3.3",
|
|
100
100
|
"picocolors": "^1.1.1",
|
|
101
|
-
"typebox": "^1.3.6",
|
|
102
101
|
"valyu-js": "^2.10.1",
|
|
103
102
|
"yaml": "^2.9.0"
|
|
104
103
|
},
|
|
105
104
|
"devDependencies": {
|
|
106
105
|
"@biomejs/biome": "^2.5.4",
|
|
107
|
-
"@earendil-works/pi-ai": "^0.
|
|
108
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
109
|
-
"@earendil-works/pi-tui": "^0.
|
|
106
|
+
"@earendil-works/pi-ai": "^0.99.0",
|
|
107
|
+
"@earendil-works/pi-coding-agent": "^0.99.0",
|
|
108
|
+
"@earendil-works/pi-tui": "^0.99.0",
|
|
110
109
|
"@types/node": "^26.1.1",
|
|
111
110
|
"esbuild": "^0.28.1",
|
|
112
111
|
"typescript": "^7.0.2",
|
|
@@ -114,7 +113,8 @@
|
|
|
114
113
|
},
|
|
115
114
|
"peerDependencies": {
|
|
116
115
|
"@earendil-works/pi-coding-agent": "*",
|
|
117
|
-
"@earendil-works/pi-tui": "*"
|
|
116
|
+
"@earendil-works/pi-tui": "*",
|
|
117
|
+
"typebox": "*"
|
|
118
118
|
},
|
|
119
119
|
"peerDependenciesMeta": {
|
|
120
120
|
"@earendil-works/pi-coding-agent": {
|