@spendgraph/tools 0.5.0 → 0.7.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/LICENSE +202 -0
- package/README.md +26 -258
- package/dist/agents/index.d.ts +1 -0
- package/dist/agents/index.js +1 -0
- package/dist/agents/moa/design.js +5 -0
- package/dist/agents/moa/http.js +1 -0
- package/dist/{builtin/write-document/write-document.d.ts → agents/moa/index.d.ts} +7 -2
- package/dist/agents/moa/index.js +1 -0
- package/dist/{builtin/write-document/commission.d.ts → agents/moa/moa.d.ts} +15 -15
- package/dist/agents/moa/moa.js +13 -0
- package/dist/{builtin/write-document → agents/moa}/types.d.ts +9 -9
- package/dist/agents/moa/types.js +0 -0
- package/dist/bogus/ask-mum.d.ts +5 -0
- package/dist/bogus/ask-mum.js +1 -0
- package/dist/bogus/book-taxi.d.ts +9 -0
- package/dist/bogus/book-taxi.js +1 -0
- package/dist/bogus/bus.d.ts +8 -0
- package/dist/bogus/bus.js +1 -0
- package/dist/bogus/calculator.d.ts +14 -0
- package/dist/bogus/calculator.js +1 -0
- package/dist/bogus/dictionary.d.ts +6 -0
- package/dist/bogus/dictionary.js +1 -0
- package/dist/bogus/feed-cat.d.ts +8 -0
- package/dist/bogus/feed-cat.js +1 -0
- package/dist/bogus/football-score.d.ts +6 -0
- package/dist/bogus/football-score.js +1 -0
- package/dist/bogus/gold-price.d.ts +7 -0
- package/dist/bogus/gold-price.js +1 -0
- package/dist/bogus/index.d.ts +14 -0
- package/dist/bogus/index.js +1 -0
- package/dist/bogus/my-calendar.d.ts +6 -0
- package/dist/bogus/my-calendar.js +1 -0
- package/dist/bogus/my-notes.d.ts +6 -0
- package/dist/bogus/my-notes.js +1 -0
- package/dist/bogus/my-recipes.d.ts +6 -0
- package/dist/bogus/my-recipes.js +1 -0
- package/dist/bogus/order-pizza.d.ts +12 -0
- package/dist/bogus/order-pizza.js +1 -0
- package/dist/bogus/prayer-times.d.ts +6 -0
- package/dist/bogus/prayer-times.js +1 -0
- package/dist/bogus/receipt.d.ts +8 -0
- package/dist/bogus/receipt.js +1 -0
- package/dist/builtin/calculate/calculate.d.ts +2 -0
- package/dist/builtin/calculate/calculate.js +1 -21
- package/dist/builtin/calculate/exact.d.ts +16 -0
- package/dist/builtin/calculate/exact.js +1 -0
- package/dist/builtin/calculate/index.d.ts +2 -1
- package/dist/builtin/calculate/index.js +1 -2
- package/dist/builtin/calculate/parse.js +1 -137
- package/dist/builtin/clock/clock.js +1 -61
- package/dist/builtin/clock/index.js +1 -1
- package/dist/builtin/code-exec/code-exec.d.ts +42 -0
- package/dist/builtin/code-exec/code-exec.js +1 -0
- package/dist/builtin/code-exec/index.d.ts +1 -0
- package/dist/builtin/code-exec/index.js +1 -0
- package/dist/builtin/date/date.d.ts +1 -1
- package/dist/builtin/date/date.js +1 -41
- package/dist/builtin/date/index.d.ts +1 -0
- package/dist/builtin/date/index.js +1 -2
- package/dist/builtin/date/shift.d.ts +1 -1
- package/dist/builtin/date/shift.js +1 -47
- package/dist/builtin/date/words.d.ts +13 -0
- package/dist/builtin/date/words.js +1 -0
- package/dist/builtin/decline/decline.d.ts +49 -0
- package/dist/builtin/decline/decline.js +1 -0
- package/dist/builtin/decline/index.d.ts +1 -0
- package/dist/builtin/decline/index.js +1 -0
- package/dist/builtin/escalate/escalate.d.ts +19 -0
- package/dist/builtin/escalate/escalate.js +1 -20
- package/dist/builtin/escalate/index.d.ts +1 -1
- package/dist/builtin/escalate/index.js +1 -1
- package/dist/builtin/extract/extract.d.ts +25 -0
- package/dist/builtin/extract/extract.js +1 -0
- package/dist/builtin/extract/index.d.ts +3 -0
- package/dist/builtin/extract/index.js +1 -0
- package/dist/builtin/extract/kinds.d.ts +6 -0
- package/dist/builtin/extract/kinds.js +1 -0
- package/dist/builtin/extract/readers.d.ts +7 -0
- package/dist/builtin/extract/readers.js +9 -0
- package/dist/builtin/finish/finish.d.ts +4 -0
- package/dist/builtin/finish/finish.js +1 -26
- package/dist/builtin/finish/index.js +1 -1
- package/dist/builtin/http/allow.d.ts +2 -0
- package/dist/builtin/http/allow.js +1 -37
- package/dist/builtin/http/hops.d.ts +10 -0
- package/dist/builtin/http/hops.js +1 -0
- package/dist/builtin/http/http.d.ts +9 -1
- package/dist/builtin/http/http.js +1 -41
- package/dist/builtin/http/index.d.ts +2 -0
- package/dist/builtin/http/index.js +1 -2
- package/dist/builtin/http/retry-after.d.ts +14 -0
- package/dist/builtin/http/retry-after.js +1 -0
- package/dist/builtin/index.d.ts +11 -6
- package/dist/builtin/index.js +1 -11
- package/dist/builtin/json/index.d.ts +1 -0
- package/dist/builtin/json/index.js +1 -2
- package/dist/builtin/json/json.js +1 -37
- package/dist/builtin/json/parse.d.ts +15 -0
- package/dist/builtin/json/parse.js +1 -0
- package/dist/builtin/json/path.js +1 -56
- package/dist/builtin/notes/index.d.ts +1 -0
- package/dist/builtin/notes/index.js +1 -0
- package/dist/builtin/notes/notes.d.ts +45 -0
- package/dist/builtin/notes/notes.js +2 -0
- package/dist/builtin/openalex/api.d.ts +22 -0
- package/dist/builtin/openalex/api.js +1 -0
- package/dist/builtin/openalex/index.d.ts +4 -0
- package/dist/builtin/openalex/index.js +1 -0
- package/dist/builtin/openalex/openalex.d.ts +23 -0
- package/dist/builtin/openalex/openalex.js +1 -0
- package/dist/builtin/openalex/render.d.ts +2 -0
- package/dist/builtin/openalex/render.js +4 -0
- package/dist/builtin/polite.d.ts +14 -0
- package/dist/builtin/polite.js +1 -0
- package/dist/builtin/publish/index.d.ts +1 -0
- package/dist/builtin/publish/index.js +1 -0
- package/dist/builtin/publish/publish.d.ts +46 -0
- package/dist/builtin/publish/publish.js +1 -0
- package/dist/builtin/think/index.js +1 -1
- package/dist/builtin/think/think.js +1 -12
- package/dist/builtin/web-search/index.js +1 -1
- package/dist/builtin/web-search/web-search.d.ts +10 -30
- package/dist/builtin/web-search/web-search.js +1 -103
- package/dist/builtin/wikidata/api.d.ts +40 -0
- package/dist/builtin/wikidata/api.js +1 -0
- package/dist/builtin/wikidata/index.d.ts +5 -0
- package/dist/builtin/wikidata/index.js +1 -0
- package/dist/builtin/wikidata/render.d.ts +13 -0
- package/dist/builtin/wikidata/render.js +2 -0
- package/dist/builtin/wikidata/wikidata.d.ts +21 -0
- package/dist/builtin/wikidata/wikidata.js +1 -0
- package/dist/bus/args.d.ts +11 -0
- package/dist/bus/args.js +1 -0
- package/dist/bus/bus.d.ts +23 -1
- package/dist/bus/bus.js +1 -49
- package/dist/bus/group.d.ts +21 -0
- package/dist/bus/group.js +1 -0
- package/dist/bus/index.d.ts +4 -0
- package/dist/bus/index.js +1 -2
- package/dist/bus/invoke.d.ts +1 -1
- package/dist/bus/invoke.js +3 -43
- package/dist/bus/namespace.d.ts +21 -0
- package/dist/bus/namespace.js +1 -0
- package/dist/bus/once.d.ts +32 -0
- package/dist/bus/once.js +1 -0
- package/dist/bus/only.d.ts +29 -0
- package/dist/bus/only.js +0 -0
- package/dist/bus/options.d.ts +31 -0
- package/dist/bus/options.js +0 -1
- package/dist/bus/output.d.ts +26 -0
- package/dist/bus/output.js +2 -0
- package/dist/bus/overlap.js +1 -31
- package/dist/bus/score.d.ts +11 -9
- package/dist/bus/score.js +1 -12
- package/dist/bus/select.d.ts +2 -17
- package/dist/bus/select.js +1 -44
- package/dist/bus/spent.d.ts +16 -0
- package/dist/bus/spent.js +1 -0
- package/dist/bus/step.js +1 -11
- package/dist/bus/terms.d.ts +25 -0
- package/dist/bus/terms.js +1 -0
- package/dist/bus/trace.d.ts +14 -1
- package/dist/bus/trace.js +1 -22
- package/dist/index.d.ts +16 -4
- package/dist/index.js +1 -3
- package/dist/internals.d.ts +9 -2
- package/dist/internals.js +1 -10
- package/dist/memories/client.d.ts +91 -0
- package/dist/memories/client.js +0 -0
- package/dist/memories/context.d.ts +5 -0
- package/dist/memories/context.js +1 -0
- package/dist/memories/deep-recall/deep-recall.d.ts +67 -0
- package/dist/memories/deep-recall/deep-recall.js +1 -0
- package/dist/memories/deep-recall/index.d.ts +1 -0
- package/dist/memories/deep-recall/index.js +1 -0
- package/dist/memories/explore/explore.d.ts +18 -0
- package/dist/memories/explore/explore.js +7 -0
- package/dist/memories/explore/subgraph.d.ts +12 -0
- package/dist/memories/explore/subgraph.js +3 -0
- package/dist/memories/index.d.ts +30 -0
- package/dist/memories/index.js +1 -0
- package/dist/memories/observe/observe.d.ts +7 -0
- package/dist/memories/observe/observe.js +1 -0
- package/dist/memories/recall/recall.d.ts +42 -0
- package/dist/memories/recall/recall.js +1 -0
- package/dist/memories/recall/render.d.ts +5 -0
- package/dist/memories/recall/render.js +3 -0
- package/dist/memories/remember/remember.d.ts +49 -0
- package/dist/memories/remember/remember.js +1 -0
- package/dist/search/index.d.ts +5 -0
- package/dist/search/index.js +1 -0
- package/dist/search/retry.d.ts +6 -0
- package/dist/search/retry.js +1 -0
- package/dist/search/search.d.ts +63 -0
- package/dist/search/search.js +1 -0
- package/dist/search/searxng/index.d.ts +2 -0
- package/dist/search/searxng/index.js +1 -0
- package/dist/search/searxng/searxng.d.ts +14 -0
- package/dist/search/searxng/searxng.js +5 -0
- package/dist/search/searxng/text.d.ts +2 -0
- package/dist/search/searxng/text.js +3 -0
- package/dist/search/sonar/index.d.ts +2 -0
- package/dist/search/sonar/index.js +1 -0
- package/dist/search/sonar/reply.js +1 -0
- package/dist/search/sonar/sonar.d.ts +13 -0
- package/dist/search/sonar/sonar.js +1 -0
- package/dist/search/tavily/index.d.ts +4 -0
- package/dist/search/tavily/index.js +1 -0
- package/dist/search/tavily/reply.d.ts +22 -0
- package/dist/search/tavily/reply.js +5 -0
- package/dist/search/tavily/tavily.d.ts +14 -0
- package/dist/search/tavily/tavily.js +1 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +1 -0
- package/dist/testing/replay.d.ts +55 -0
- package/dist/testing/replay.js +1 -0
- package/dist/testing/store.d.ts +35 -0
- package/dist/testing/store.js +4 -0
- package/dist/tool/bind.js +1 -64
- package/dist/tool/index.js +1 -2
- package/dist/tool/tool.d.ts +5 -1
- package/dist/tool/tool.js +1 -51
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.js +0 -1
- package/dist/types/result.d.ts +38 -0
- package/dist/types/result.js +0 -1
- package/dist/types/tool.d.ts +74 -0
- package/dist/types/tool.js +0 -1
- package/dist/wire/index.js +1 -3
- package/dist/wire/json-schema.js +1 -65
- package/dist/wire/markdown.d.ts +5 -3
- package/dist/wire/markdown.js +9 -29
- package/dist/wire/providers.js +1 -18
- package/docs/bogus.mdx +85 -0
- package/docs/builtins.mdx +156 -0
- package/docs/bus.mdx +90 -0
- package/docs/declaring.mdx +85 -0
- package/docs/effects.mdx +49 -0
- package/docs/overview.mdx +76 -0
- package/docs/selecting.mdx +79 -0
- package/docs/stored.mdx +83 -0
- package/docs/turns.mdx +57 -0
- package/package.json +28 -11
- package/dist/builtin/deep-recall/deep-recall.d.ts +0 -27
- package/dist/builtin/deep-recall/deep-recall.js +0 -45
- package/dist/builtin/deep-recall/index.d.ts +0 -1
- package/dist/builtin/deep-recall/index.js +0 -1
- package/dist/builtin/web-search/reply.js +0 -27
- package/dist/builtin/write-document/commission.js +0 -152
- package/dist/builtin/write-document/design.js +0 -86
- package/dist/builtin/write-document/http.js +0 -71
- package/dist/builtin/write-document/index.d.ts +0 -6
- package/dist/builtin/write-document/index.js +0 -4
- package/dist/builtin/write-document/types.js +0 -1
- package/dist/builtin/write-document/write-document.js +0 -142
- /package/dist/{builtin/write-document → agents/moa}/design.d.ts +0 -0
- /package/dist/{builtin/write-document → agents/moa}/http.d.ts +0 -0
- /package/dist/{builtin/web-search → search/sonar}/reply.d.ts +0 -0
|
@@ -1,37 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { readPath } from "./path.js";
|
|
3
|
-
const DEFAULT_MAX_CHARS = 20_000;
|
|
4
|
-
export function jsonQuery(opts = {}) {
|
|
5
|
-
const maxChars = opts.maxChars ?? DEFAULT_MAX_CHARS;
|
|
6
|
-
return tool({
|
|
7
|
-
name: "json_query",
|
|
8
|
-
effect: "readonly",
|
|
9
|
-
description: "Reads one value out of a JSON document by path. Paths look like " +
|
|
10
|
-
"`invoices[0].total`, or `invoices[*].total` for every element. Use it instead " +
|
|
11
|
-
"of reading a long document yourself.",
|
|
12
|
-
args: [
|
|
13
|
-
{ name: "json", type: "text", required: true, maxLength: 200_000 },
|
|
14
|
-
{ name: "path", type: "string", required: true, maxLength: 500 },
|
|
15
|
-
],
|
|
16
|
-
run: ({ json, path }) => {
|
|
17
|
-
let document;
|
|
18
|
-
try {
|
|
19
|
-
document = JSON.parse(json);
|
|
20
|
-
}
|
|
21
|
-
catch (err) {
|
|
22
|
-
throw new Error(`json is not valid JSON: ${err.message}`);
|
|
23
|
-
}
|
|
24
|
-
const value = readPath(document, path);
|
|
25
|
-
if (value === undefined)
|
|
26
|
-
return { found: false, path, value: null };
|
|
27
|
-
const rendered = JSON.stringify(value) ?? "null";
|
|
28
|
-
return {
|
|
29
|
-
found: true,
|
|
30
|
-
path,
|
|
31
|
-
value,
|
|
32
|
-
truncated: rendered.length > maxChars,
|
|
33
|
-
...(rendered.length > maxChars ? { value: `${rendered.slice(0, maxChars)}…` } : {}),
|
|
34
|
-
};
|
|
35
|
-
},
|
|
36
|
-
});
|
|
37
|
-
}
|
|
1
|
+
import{tool as s}from"../../tool/index.js";import{jsonFrom as i}from"./parse.js";import{readPath as l}from"./path.js";const d=2e4;function g(r={}){const e=r.maxChars??d;return s({name:"json_query",effect:"readonly",description:"Reads one value out of a JSON document by path. Paths look like `invoices[0].total`, or `invoices[*].total` for every element. Use it instead of reading a long document yourself.",args:[{name:"json",type:"text",required:!0,maxLength:2e5},{name:"path",type:"string",required:!0,maxLength:500}],run:({json:a,path:n})=>{const u=i(a),t=l(u,n);if(t===void 0)return{found:!1,path:n,value:null};const o=JSON.stringify(t)??"null";return{found:!0,path:n,value:t,truncated:o.length>e,...o.length>e?{value:`${o.slice(0,e)}\u2026`}:{}}}})}export{g as jsonQuery};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The JSON inside a model's reply, whatever it wrapped it in.
|
|
3
|
+
*
|
|
4
|
+
* The two commonest structured-output failures are not bad JSON at all: a
|
|
5
|
+
* fenced block, and a sentence before or after it. Both parse perfectly once
|
|
6
|
+
* the payload is found, and every caller otherwise writes the same fence
|
|
7
|
+
* stripping by hand and gets the second case wrong.
|
|
8
|
+
*
|
|
9
|
+
* It finds the JSON; it never rewrites it. A trailing comma, a single-quoted
|
|
10
|
+
* key or a missing brace stays an error, because repairing those is guessing at
|
|
11
|
+
* what was meant and a wrong guess is a confidently wrong record. Those belong
|
|
12
|
+
* in a bounded repair round, where the model is told what failed and answers
|
|
13
|
+
* again.
|
|
14
|
+
*/
|
|
15
|
+
export declare function jsonFrom(text: string): unknown;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const l=/```(?:[a-zA-Z0-9_-]+)?\s*\n?([\s\S]*?)```/,f={"{":"}","[":"]"};function a(e){const n=e.search(/[[{]/);if(n===-1)return null;const t=f[e[n]];let o=0,i=!1,s=!1;for(let r=n;r<e.length;r+=1){const c=e[r];if(s){s=!1;continue}if(c==="\\"&&i){s=!0;continue}if(c==='"'){i=!i;continue}if(!i){if(c===e[n])o+=1;else if(c===t&&(o-=1,o===0))return e.slice(n,r+1)}}return null}function u(e){const n=e.trim();for(const t of[n,l.exec(n)?.[1]?.trim(),a(n)])if(t)try{return JSON.parse(t)}catch{}throw new Error(`No JSON in ${n.length>80?`${n.slice(0,80)}\u2026`:n||"an empty reply"}`)}export{u as jsonFrom};
|
|
@@ -1,56 +1 @@
|
|
|
1
|
-
function
|
|
2
|
-
const segments = [];
|
|
3
|
-
for (const part of path.split(".")) {
|
|
4
|
-
if (!part)
|
|
5
|
-
continue;
|
|
6
|
-
const [, name, brackets] = /^([^[\]]*)((?:\[[^\]]*\])*)$/.exec(part) ?? [];
|
|
7
|
-
if (name === undefined)
|
|
8
|
-
throw new Error(`"${part}" is not a path segment.`);
|
|
9
|
-
if (name)
|
|
10
|
-
segments.push({ kind: "key", name });
|
|
11
|
-
for (const [, inner] of (brackets ?? "").matchAll(/\[([^\]]*)\]/g)) {
|
|
12
|
-
if (inner === "*") {
|
|
13
|
-
segments.push({ kind: "all" });
|
|
14
|
-
continue;
|
|
15
|
-
}
|
|
16
|
-
const at = Number(inner);
|
|
17
|
-
if (!Number.isInteger(at))
|
|
18
|
-
throw new Error(`"[${inner}]" is not an index or a *.`);
|
|
19
|
-
segments.push({ kind: "index", at });
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
return segments;
|
|
23
|
-
}
|
|
24
|
-
function step(value, segment) {
|
|
25
|
-
if (value === null || value === undefined)
|
|
26
|
-
return undefined;
|
|
27
|
-
if (segment.kind === "key") {
|
|
28
|
-
if (typeof value !== "object" || Array.isArray(value))
|
|
29
|
-
return undefined;
|
|
30
|
-
return value[segment.name];
|
|
31
|
-
}
|
|
32
|
-
if (!Array.isArray(value))
|
|
33
|
-
return undefined;
|
|
34
|
-
if (segment.kind === "index") {
|
|
35
|
-
return value.at(segment.at);
|
|
36
|
-
}
|
|
37
|
-
return value;
|
|
38
|
-
}
|
|
39
|
-
export function readPath(document, path) {
|
|
40
|
-
let cursor = document;
|
|
41
|
-
let spread = false;
|
|
42
|
-
for (const segment of parsePath(path)) {
|
|
43
|
-
if (segment.kind === "all") {
|
|
44
|
-
if (!Array.isArray(cursor))
|
|
45
|
-
return undefined;
|
|
46
|
-
cursor = spread ? cursor.flat() : [...cursor];
|
|
47
|
-
spread = true;
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
cursor =
|
|
51
|
-
spread && Array.isArray(cursor)
|
|
52
|
-
? cursor.map((item) => step(item, segment))
|
|
53
|
-
: step(cursor, segment);
|
|
54
|
-
}
|
|
55
|
-
return cursor;
|
|
56
|
-
}
|
|
1
|
+
function a(r){const e=[];for(const n of r.split(".")){if(!n)continue;const[,t,i]=/^([^[\]]*)((?:\[[^\]]*\])*)$/.exec(n)??[];if(t===void 0)throw new Error(`"${n}" is not a path segment.`);t&&e.push({kind:"key",name:t});for(const[,f]of(i??"").matchAll(/\[([^\]]*)\]/g)){if(f==="*"){e.push({kind:"all"});continue}const o=Number(f);if(!Number.isInteger(o))throw new Error(`"[${f}]" is not an index or a *.`);e.push({kind:"index",at:o})}}return e}function s(r,e){if(r!=null){if(e.kind==="key")return typeof r!="object"||Array.isArray(r)?void 0:r[e.name];if(Array.isArray(r))return e.kind==="index"?r.at(e.at):r}}function d(r,e){let n=r,t=!1;for(const i of a(e)){if(i.kind==="all"){if(!Array.isArray(n))return;n=t?n.flat():[...n],t=!0;continue}n=t&&Array.isArray(n)?n.map(f=>s(f,i)):s(n,i)}return n}export{d as readPath};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ACTIONS, type NotesOptions, type NotesStore, notes, notesStore } from "./notes.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{ACTIONS as t,notes as r,notesStore as n}from"./notes.js";export{t as ACTIONS,r as notes,n as notesStore};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** Boards by session, one per process unless you hand every bus the same one. */
|
|
2
|
+
export type NotesStore = Map<string, string[]>;
|
|
3
|
+
export interface NotesOptions {
|
|
4
|
+
/** Whose board: a session id, so a follow-up question sees what the last run worked out. */
|
|
5
|
+
session: string;
|
|
6
|
+
/** Where boards live. Default one store for the process; pass your own to share or to inspect. */
|
|
7
|
+
store?: NotesStore;
|
|
8
|
+
/** Notes a board holds before `post` is refused. Default 50. */
|
|
9
|
+
maxNotes?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare const ACTIONS: readonly ["post", "read", "replace", "clear"];
|
|
12
|
+
export declare function notesStore(): NotesStore;
|
|
13
|
+
/**
|
|
14
|
+
* A board the model writes to and reads back during a session — what it is
|
|
15
|
+
* doing, what is still missing, what it decided — kept in memory and gone when
|
|
16
|
+
* the process is.
|
|
17
|
+
*
|
|
18
|
+
* `think` is write-once and never seen again; this is the model's own chosen
|
|
19
|
+
* state, readable three steps later. It is not memory: a note worth keeping
|
|
20
|
+
* goes to `remember`, on purpose, so half-thoughts never surface as facts.
|
|
21
|
+
*
|
|
22
|
+
* `replace` is what makes it usable as a running plan. Append-only, a plan
|
|
23
|
+
* re-posted each turn becomes twenty notes where the twentieth contradicts the
|
|
24
|
+
* third and `read` shows both — and the only way back is `clear`, which throws
|
|
25
|
+
* the plan away with the noise. One note, rewritten, is the whole pattern.
|
|
26
|
+
*
|
|
27
|
+
* No effect label: `read` changes nothing and `post` changes only this board.
|
|
28
|
+
*/
|
|
29
|
+
export declare function notes(opts: NotesOptions): import("../../index.js").Tool<import("../../index.js").ArgsOf<readonly [{
|
|
30
|
+
readonly name: "action";
|
|
31
|
+
readonly type: "enum";
|
|
32
|
+
readonly required: true;
|
|
33
|
+
readonly options: readonly ["post", "read", "replace", "clear"];
|
|
34
|
+
}, {
|
|
35
|
+
readonly name: "note";
|
|
36
|
+
readonly type: "text";
|
|
37
|
+
readonly required: false;
|
|
38
|
+
readonly maxLength: 2000;
|
|
39
|
+
}, {
|
|
40
|
+
readonly name: "number";
|
|
41
|
+
readonly type: "number";
|
|
42
|
+
readonly required: false;
|
|
43
|
+
readonly min: 1;
|
|
44
|
+
readonly description: "Which note to replace, as `read` numbers them.";
|
|
45
|
+
}]>>;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{tool as d}from"../../tool/index.js";const l=["post","read","replace","clear"],u=50,c=2e3,w=new Map;function p(){return new Map}function m(e){return e.map((t,n)=>`#${n+1} ${t}`).join(`
|
|
2
|
+
`)}function f(e,t,n){if(t===void 0)throw new Error("replace needs the `number` of the note.");if(e.length===0)throw new Error("There is nothing on the board to replace.");if(t<1||t>e.length)throw new Error(`There is no note #${t}; the board holds #1 to #${e.length}.`);const o=[...e];return o[t-1]=n,o}function y(e){if(!e.session?.trim())throw new Error("notes needs the session whose board this is.");const t=e.store??w,n=e.maxNotes??u,o=()=>t.get(e.session)??[];return d({name:"notes",description:"Your notes for this session. `post` writes one \u2014 what you are doing, what is still missing, what you decided. `read` shows them all, numbered, in order. `replace` rewrites one by its number \u2014 keep a plan as a single note and rewrite it as you go, rather than posting its status again each turn. `clear` wipes them. They last the session and nobody else reads them; use `remember` for anything that should outlast it.",args:[{name:"action",type:"enum",required:!0,options:[...l]},{name:"note",type:"text",required:!1,maxLength:c},{name:"number",type:"number",required:!1,min:1,description:"Which note to replace, as `read` numbers them."}],run:({action:s,note:h,number:a})=>{const r=o();if(s==="read")return r.length?m(r):"Nothing on the board yet.";if(s==="clear")return t.delete(e.session),`Cleared ${r.length} note${r.length===1?"":"s"}.`;const i=h?.trim();if(!i)throw new Error(`${s} needs a \`note\`.`);if(s==="replace")return t.set(e.session,f(r,a,i)),`#${a} rewritten.`;if(r.length>=n)throw new Error(`The board holds ${n} notes; clear it, or say less.`);return t.set(e.session,[...r,i]),`#${r.length+1} noted.`}})}export{l as ACTIONS,y as notes,p as notesStore};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type PoliteOptions } from "../polite.js";
|
|
2
|
+
export interface Paper {
|
|
3
|
+
title: string;
|
|
4
|
+
year: number | null;
|
|
5
|
+
authors: string[];
|
|
6
|
+
venue: string | null;
|
|
7
|
+
citations: number;
|
|
8
|
+
doi: string | null;
|
|
9
|
+
pdf: string | null;
|
|
10
|
+
abstract: string;
|
|
11
|
+
}
|
|
12
|
+
export interface OpenAlexApi {
|
|
13
|
+
search(query: string, limit: number): Promise<Paper[]>;
|
|
14
|
+
}
|
|
15
|
+
export interface ApiOptions extends Omit<PoliteOptions, "userAgent"> {
|
|
16
|
+
/** Your address, sent as `mailto`; OpenAlex answers named callers from a faster pool. */
|
|
17
|
+
mailto?: string;
|
|
18
|
+
}
|
|
19
|
+
/** OpenAlex stores an abstract as word → positions; this puts the words back in order. */
|
|
20
|
+
export declare function abstractOf(index: Record<string, number[]> | null | undefined): string;
|
|
21
|
+
/** The one OpenAlex call the tool makes, behind an interface a test can fake. */
|
|
22
|
+
export declare function openAlexApi(opts?: ApiOptions): OpenAlexApi;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{politeGet as n}from"../polite.js";const i="https://api.openalex.org/works",c="spendgraph-tools (https://spendgraph.locusgraph.com)",l=["id","doi","title","publication_year","cited_by_count","authorships","primary_location","open_access","abstract_inverted_index"].join(",");function p(t){if(!t)return"";const e=[];for(const[o,s]of Object.entries(t))for(const r of s)e[r]=o;return e.filter(o=>o!==void 0).join(" ")}function u(t){return{title:t.title?.trim()||"(untitled)",year:t.publication_year??null,authors:(t.authorships??[]).flatMap(e=>e.author?.display_name?[e.author.display_name]:[]),venue:t.primary_location?.source?.display_name??null,citations:t.cited_by_count??0,doi:t.doi?.replace(/^https?:\/\/doi\.org\//,"")??null,pdf:t.open_access?.oa_url??t.primary_location?.pdf_url??null,abstract:p(t.abstract_inverted_index)}}function h(t={}){const e=n({...t,userAgent:c});return{async search(o,s){const r=new URL(i);r.searchParams.set("search",o),r.searchParams.set("per-page",String(s)),r.searchParams.set("select",l),t.mailto&&r.searchParams.set("mailto",t.mailto);const a=await e(r);if(!a.ok)throw new Error(`OpenAlex answered ${a.status}.`);return((await a.json()).results??[]).map(u)}}}export{p as abstractOf,h as openAlexApi};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{abstractOf as o,openAlexApi as p}from"./api.js";import{openalex as x}from"./openalex.js";import{rendered as a}from"./render.js";export{o as abstractOf,p as openAlexApi,x as openalex,a as rendered};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ApiOptions, type OpenAlexApi } from "./api.js";
|
|
2
|
+
export interface OpenAlexOptions extends ApiOptions {
|
|
3
|
+
/** Papers returned. Default 5. */
|
|
4
|
+
maxResults?: number;
|
|
5
|
+
/** Abstract kept per paper. Default 400 characters. */
|
|
6
|
+
maxAbstractChars?: number;
|
|
7
|
+
/** Injected for tests, in place of the call to OpenAlex. */
|
|
8
|
+
api?: OpenAlexApi;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Papers on a topic from OpenAlex, as text — title, authors, venue, how often
|
|
12
|
+
* cited, a DOI, a PDF when one is open, and the start of the abstract.
|
|
13
|
+
*
|
|
14
|
+
* Free and unauthenticated. For what the literature says; not for anything
|
|
15
|
+
* recent, commercial, or outside a published paper.
|
|
16
|
+
*/
|
|
17
|
+
export declare function openalex(opts?: OpenAlexOptions): import("../../index.js").Tool<import("../../index.js").ArgsOf<readonly [{
|
|
18
|
+
readonly name: "question";
|
|
19
|
+
readonly type: "string";
|
|
20
|
+
readonly required: true;
|
|
21
|
+
readonly maxLength: 300;
|
|
22
|
+
readonly description: "The topic, as a phrase a paper title or abstract would contain.";
|
|
23
|
+
}]>>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{tool as i}from"../../tool/index.js";import{openAlexApi as c}from"./api.js";import{rendered as h}from"./render.js";const p=5,m=400;function l(t={}){const a=t.api??c(t),n=t.maxResults??p,o=t.maxAbstractChars??m;return i({name:"paper_search",effect:"readonly",trust:"external",description:"Finds published papers on a topic and returns them as text: title, authors, venue, citation count, DOI, an open PDF when there is one, and the start of the abstract. Use it for what research has found or measured, and cite the DOI. Not for prices, products, news or anything from the last few months.",args:[{name:"question",type:"string",required:!0,maxLength:300,description:"The topic, as a phrase a paper title or abstract would contain."}],run:async({question:s})=>{const e=s.trim();if(!e)throw new Error("Name the topic to search for.");const r=await a.search(e,n);if(r.length===0)throw new Error(`OpenAlex has no papers matching "${e}".`);return h(e,r,o)}})}export{l as openalex};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
const h=3;function c(n){const t=n.authors.slice(0,3).map(e=>e.split(" ").at(-1)??e),o=n.authors.length-t.length;return t.length===0?"unknown authors":o>0?`${t.join(", ")} +${o}`:t.join(", ")}function $(n){const t=n.year===null?"":` (${n.year})`,o=n.venue?` \xB7 ${n.venue}`:"";return`${n.title}${t} \u2014 ${c(n)}${o} \xB7 ${n.citations} citations`}function d(n){const o=[n.doi?`doi:${n.doi}`:null,n.pdf?`pdf: ${n.pdf}`:null].filter(e=>e!==null);return o.length>0?o.join(" \xB7 "):null}function f(n,t){const o=n.abstract.trim();return o?o.length>t?`${o.slice(0,t).trimEnd()}\u2026`:o:null}function m(n,t,o){const e=`${t.length} paper${t.length===1?"":"s"} on "${n}", most relevant first`,u=t.map((l,s)=>[`${s+1}. ${$(l)}`,d(l),f(l,o)].filter(i=>i!==null).map((i,r)=>r===0?i:` ${i}`).join(`
|
|
2
|
+
`));return[e,...u].join(`
|
|
3
|
+
|
|
4
|
+
`)}export{m as rendered};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface PoliteOptions {
|
|
2
|
+
/** Named on every request, as the open APIs ask, so a shared IP is still identifiable. */
|
|
3
|
+
userAgent: string;
|
|
4
|
+
timeoutMs?: number;
|
|
5
|
+
/** Injected for tests. */
|
|
6
|
+
fetch?: typeof fetch;
|
|
7
|
+
/** Injected for tests, in place of sleeping out a Retry-After. */
|
|
8
|
+
wait?: (ms: number) => Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A GET that behaves the way the free, keyless APIs ask: named, timed out,
|
|
12
|
+
* and waiting out a 429 for as long as Retry-After says before asking again.
|
|
13
|
+
*/
|
|
14
|
+
export declare function politeGet(opts: PoliteOptions): (url: URL) => Promise<Response>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const a=429,_=2,c=1e3,R=5e3,u=1e4,o=t=>new Promise(e=>setTimeout(e,t));function E(t){const e=Number(t.headers.get("retry-after")),n=Number.isFinite(e)&&e>0?e*1e3:1e3;return Math.min(n,5e3)}function A(t){const e=t.fetch??globalThis.fetch.bind(globalThis),n=t.wait??o,T=t.timeoutMs??1e4;return async i=>{for(let r=0;;r++){const s=await e(i,{signal:AbortSignal.timeout(T),headers:{"user-agent":t.userAgent,"api-user-agent":t.userAgent}});if(s.status!==429||r>=2)return s;await n(E(s))}}}export{A as politeGet};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { type Publisher, type PublishOptions, publish } from "./publish.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{publish as r}from"./publish.js";export{r as publish};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { PublishedFile, PublishInput, PublishTo } from "@spendgraph/sdk";
|
|
2
|
+
/** The part of the spendgraph client this needs: `new Spendgraph({ apiKey }).files` satisfies it. */
|
|
3
|
+
export interface Publisher {
|
|
4
|
+
publish(input: PublishInput): Promise<{
|
|
5
|
+
file: PublishedFile;
|
|
6
|
+
}>;
|
|
7
|
+
}
|
|
8
|
+
export interface PublishOptions {
|
|
9
|
+
/** Reaches the app with the key and the project pin; the tool never holds a credential. */
|
|
10
|
+
files: Publisher;
|
|
11
|
+
/** The project a file is filed under. A key publishes only into its own. */
|
|
12
|
+
projectId: string;
|
|
13
|
+
/** Formats this deployment will produce. Default all four. */
|
|
14
|
+
formats?: readonly PublishTo[];
|
|
15
|
+
/** Longest content accepted. Default 200000 characters. */
|
|
16
|
+
maxChars?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Content out as a file at a public URL — markdown, a page, plain text or a
|
|
20
|
+
* PDF — so a model can hand a person something to open, not a wall of text.
|
|
21
|
+
*
|
|
22
|
+
* The app renders and stores it; this only asks. A new file every call, so it
|
|
23
|
+
* carries no effect label: a repeat is a second file, never a lost one.
|
|
24
|
+
*/
|
|
25
|
+
export declare function publish(opts: PublishOptions): import("../../index.js").Tool<import("../../index.js").ArgsOf<readonly [{
|
|
26
|
+
readonly name: "content";
|
|
27
|
+
readonly type: "text";
|
|
28
|
+
readonly required: true;
|
|
29
|
+
readonly maxLength: number;
|
|
30
|
+
}, {
|
|
31
|
+
readonly name: "to";
|
|
32
|
+
readonly type: "enum";
|
|
33
|
+
readonly required: true;
|
|
34
|
+
readonly options: readonly ("md" | "pdf" | "html" | "txt")[];
|
|
35
|
+
}, {
|
|
36
|
+
readonly name: "from";
|
|
37
|
+
readonly type: "enum";
|
|
38
|
+
readonly required: false;
|
|
39
|
+
readonly options: readonly ["text", "md", "html"];
|
|
40
|
+
}, {
|
|
41
|
+
readonly name: "name";
|
|
42
|
+
readonly type: "string";
|
|
43
|
+
readonly required: false;
|
|
44
|
+
readonly maxLength: 120;
|
|
45
|
+
readonly description: "The file's name without its extension.";
|
|
46
|
+
}]>>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{PUBLISH_FROM as m,PUBLISH_TO as u}from"@spendgraph/sdk";import{tool as h}from"../../tool/index.js";const l=2e5,d="md";function p(e){if(!e.files)throw new Error("publish needs the client's files resource to reach the app.");if(!e.projectId?.trim())throw new Error("publish needs the projectId a file is filed under.");const r=e.formats?.length?e.formats:u,o=e.maxChars??l;return h({name:"publish",description:`Turns content into a file people can open and returns its public URL. Formats: ${r.join(", ")}. Say what the content is written in \u2014 markdown unless told otherwise \u2014 and what to make of it. Use it when the person asked for a document, a report or a download, not for a short answer.`,args:[{name:"content",type:"text",required:!0,maxLength:o},{name:"to",type:"enum",required:!0,options:[...r]},{name:"from",type:"enum",required:!1,options:[...m]},{name:"name",type:"string",required:!1,maxLength:120,description:"The file's name without its extension."}],run:async({content:i,to:s,from:a,name:n})=>{const{file:t}=await e.files.publish({projectId:e.projectId,content:i,from:a??d,to:s,...n?{name:n}:{}});return`Published ${t.name} (${t.bytes} bytes): ${t.url}`}})}export{p as publish};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
import{think as t}from"./think.js";export{t as think};
|
|
@@ -1,12 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
export function think() {
|
|
3
|
-
return tool({
|
|
4
|
-
name: "think",
|
|
5
|
-
effect: "readonly",
|
|
6
|
-
description: "Think a step through before acting. Use it to check a plan against the rules, " +
|
|
7
|
-
"to work out what is still missing, or to decide between two tools. It changes " +
|
|
8
|
-
"nothing and returns nothing — it is scratch space, not an answer.",
|
|
9
|
-
args: [{ name: "thought", type: "text", required: true, maxLength: 4000 }],
|
|
10
|
-
run: () => "noted",
|
|
11
|
-
});
|
|
12
|
-
}
|
|
1
|
+
import{tool as t}from"../../tool/index.js";function n(){return t({name:"think",effect:"readonly",description:"Think a step through before acting. Use it to check a plan against the rules, to work out what is still missing, or to decide between two tools. It changes nothing and returns nothing \u2014 it is scratch space, not an answer.",args:[{name:"thought",type:"text",required:!0,maxLength:4e3}],run:()=>"noted"})}export{n as think};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
import{webSearch as o}from"./web-search.js";export{o as webSearch};
|
|
@@ -1,42 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
export type SearchDepth
|
|
1
|
+
import { type SearchDepth, type Searcher } from "../../search/search.js";
|
|
2
|
+
export type { SearchDepth };
|
|
3
3
|
export interface WebSearchOptions {
|
|
4
|
-
/**
|
|
5
|
-
|
|
4
|
+
/** Where answers come from: `sonar(...)`, `searxng(...)`, or your own. Required. */
|
|
5
|
+
search: Searcher;
|
|
6
6
|
/**
|
|
7
7
|
* Deepest tier the model may reach. Default `"pro"`.
|
|
8
8
|
*
|
|
9
|
-
* `"deep"` is opt-in because
|
|
9
|
+
* `"deep"` is opt-in because on Sonar it runs for minutes and bills
|
|
10
10
|
* reasoning and citation tokens on top of the usual two.
|
|
11
11
|
*/
|
|
12
12
|
maxDepth?: SearchDepth;
|
|
13
|
-
/** Hosts results are restricted to. Set here, so the model cannot widen it. */
|
|
14
|
-
domains?: string[];
|
|
15
|
-
/** Give up after this. Defaults to 60000, or 300000 on a deep search. */
|
|
16
|
-
timeoutMs?: number;
|
|
17
|
-
/** Longest answer handed back. Default 20000 characters. */
|
|
18
|
-
maxChars?: number;
|
|
19
|
-
/** Longest source list handed back. Default 20. */
|
|
20
|
-
maxResults?: number;
|
|
21
|
-
/**
|
|
22
|
-
* Tries for a rate limit or a bad minute on Perplexity's side. Default 3.
|
|
23
|
-
*
|
|
24
|
-
* A wave of sub-questions searches in parallel, so two calls landing together
|
|
25
|
-
* is ordinary and a 429 on the second is not a failure worth surfacing — it
|
|
26
|
-
* is a wait. `retry-after` is honoured where the server sends one.
|
|
27
|
-
*/
|
|
28
|
-
attempts?: number;
|
|
29
|
-
/** Injected for tests. */
|
|
30
|
-
fetch?: typeof fetch;
|
|
31
|
-
/** Injected for tests. */
|
|
32
|
-
sleep?: (ms: number) => Promise<void>;
|
|
33
13
|
}
|
|
34
14
|
/**
|
|
35
|
-
* The live web, through
|
|
15
|
+
* The live web, through the searcher it is built on.
|
|
36
16
|
*
|
|
37
|
-
* The tier is the model's to choose and yours to cap:
|
|
38
|
-
* minutes and orders of magnitude, and a model reaching for it unprompted
|
|
39
|
-
* the failure `maxDepth` exists to prevent. Tokens come back rather than a
|
|
17
|
+
* The tier is the model's to choose and yours to cap: on Sonar, deep research
|
|
18
|
+
* is minutes and orders of magnitude, and a model reaching for it unprompted
|
|
19
|
+
* is the failure `maxDepth` exists to prevent. Tokens come back rather than a
|
|
40
20
|
* price, because what a search costs is the pricing table's business.
|
|
41
21
|
*/
|
|
42
22
|
export declare function webSearch(opts: WebSearchOptions): import("../../index.js").Tool<import("../../index.js").ArgsOf<readonly [{
|
|
@@ -50,7 +30,7 @@ export declare function webSearch(opts: WebSearchOptions): import("../../index.j
|
|
|
50
30
|
readonly type: "enum";
|
|
51
31
|
readonly required: false;
|
|
52
32
|
readonly options: readonly ["quick", "pro", "deep"];
|
|
53
|
-
readonly description:
|
|
33
|
+
readonly description: string;
|
|
54
34
|
}, {
|
|
55
35
|
readonly name: "recency";
|
|
56
36
|
readonly type: "enum";
|
|
@@ -1,103 +1 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { readReply } from "./reply.js";
|
|
3
|
-
const DEPTHS = ["quick", "pro", "deep"];
|
|
4
|
-
const MODELS = {
|
|
5
|
-
quick: "sonar",
|
|
6
|
-
pro: "sonar-pro",
|
|
7
|
-
deep: "sonar-deep-research",
|
|
8
|
-
};
|
|
9
|
-
const ENDPOINT = "https://api.perplexity.ai/chat/completions";
|
|
10
|
-
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
11
|
-
const DEEP_TIMEOUT_MS = 300_000;
|
|
12
|
-
const DEFAULT_MAX_CHARS = 20_000;
|
|
13
|
-
const DEFAULT_MAX_RESULTS = 20;
|
|
14
|
-
const ERROR_DETAIL_CHARS = 500;
|
|
15
|
-
const BACKOFF_BASE_MS = 500;
|
|
16
|
-
const MAX_BACKOFF_MS = 8_000;
|
|
17
|
-
function retryAfterMs(res) {
|
|
18
|
-
const raw = res.headers.get("retry-after");
|
|
19
|
-
if (!raw)
|
|
20
|
-
return null;
|
|
21
|
-
const seconds = Number(raw);
|
|
22
|
-
return Number.isFinite(seconds) && seconds >= 0 ? seconds * 1000 : null;
|
|
23
|
-
}
|
|
24
|
-
const isWorthRetrying = (status) => status === 429 || status >= 500;
|
|
25
|
-
function describe(maxDepth, domains) {
|
|
26
|
-
return ("Searches the live web and returns an answer with the sources it rests on. Use it for " +
|
|
27
|
-
"anything that turns on current facts — prices, releases, who holds a post, what " +
|
|
28
|
-
"changed. quick is one pass and is right for most lookups; pro searches wider and cites " +
|
|
29
|
-
"more." +
|
|
30
|
-
(maxDepth === "deep"
|
|
31
|
-
? " deep is an exhaustive multi-step review: minutes, and many times the cost. Ask for it only when a wrong answer is expensive."
|
|
32
|
-
: " deep is not enabled here.") +
|
|
33
|
-
(domains?.length ? ` Results come only from ${domains.join(", ")}.` : ""));
|
|
34
|
-
}
|
|
35
|
-
export function webSearch(opts) {
|
|
36
|
-
if (!opts.apiKey?.trim()) {
|
|
37
|
-
throw new Error("webSearch needs a Perplexity apiKey; it will not read one from the process.");
|
|
38
|
-
}
|
|
39
|
-
const doFetch = opts.fetch ?? globalThis.fetch.bind(globalThis);
|
|
40
|
-
const maxDepth = opts.maxDepth ?? "pro";
|
|
41
|
-
const attempts = Math.max(1, opts.attempts ?? 3);
|
|
42
|
-
const pause = opts.sleep ?? ((ms) => new Promise((done) => setTimeout(done, ms)));
|
|
43
|
-
return tool({
|
|
44
|
-
name: "web_search",
|
|
45
|
-
description: describe(maxDepth, opts.domains),
|
|
46
|
-
effect: "readonly",
|
|
47
|
-
args: [
|
|
48
|
-
{
|
|
49
|
-
name: "query",
|
|
50
|
-
type: "string",
|
|
51
|
-
required: true,
|
|
52
|
-
maxLength: 1000,
|
|
53
|
-
description: "What to find out, as a question. One thing, not several.",
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
name: "depth",
|
|
57
|
-
type: "enum",
|
|
58
|
-
required: false,
|
|
59
|
-
options: ["quick", "pro", "deep"],
|
|
60
|
-
description: "Default quick. Raise it only when a first pass would not settle it.",
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
name: "recency",
|
|
64
|
-
type: "enum",
|
|
65
|
-
required: false,
|
|
66
|
-
options: ["day", "week", "month", "year"],
|
|
67
|
-
description: "Only consider pages published within this. Leave unset for anything.",
|
|
68
|
-
},
|
|
69
|
-
],
|
|
70
|
-
run: async ({ query, depth = "quick", recency }) => {
|
|
71
|
-
if (DEPTHS.indexOf(depth) > DEPTHS.indexOf(maxDepth)) {
|
|
72
|
-
throw new Error(`web_search was asked for "${depth}", above the "${maxDepth}" this deployment allows.`);
|
|
73
|
-
}
|
|
74
|
-
const model = MODELS[depth];
|
|
75
|
-
const fallback = depth === "deep" ? DEEP_TIMEOUT_MS : DEFAULT_TIMEOUT_MS;
|
|
76
|
-
for (let attempt = 1;; attempt++) {
|
|
77
|
-
const res = await doFetch(ENDPOINT, {
|
|
78
|
-
method: "POST",
|
|
79
|
-
signal: AbortSignal.timeout(opts.timeoutMs ?? fallback),
|
|
80
|
-
headers: {
|
|
81
|
-
authorization: `Bearer ${opts.apiKey}`,
|
|
82
|
-
"content-type": "application/json",
|
|
83
|
-
},
|
|
84
|
-
body: JSON.stringify({
|
|
85
|
-
model,
|
|
86
|
-
messages: [{ role: "user", content: query }],
|
|
87
|
-
...(recency ? { search_recency_filter: recency } : {}),
|
|
88
|
-
...(opts.domains?.length ? { search_domain_filter: opts.domains } : {}),
|
|
89
|
-
}),
|
|
90
|
-
});
|
|
91
|
-
if (res.ok) {
|
|
92
|
-
return readReply(await res.json(), model, opts.maxChars ?? DEFAULT_MAX_CHARS, opts.maxResults ?? DEFAULT_MAX_RESULTS);
|
|
93
|
-
}
|
|
94
|
-
const detail = (await res.text()).slice(0, ERROR_DETAIL_CHARS);
|
|
95
|
-
if (!isWorthRetrying(res.status) || attempt === attempts) {
|
|
96
|
-
throw new Error(`Perplexity returned ${res.status} for ${model}: ${detail}`);
|
|
97
|
-
}
|
|
98
|
-
const wait = retryAfterMs(res) ?? BACKOFF_BASE_MS * 2 ** (attempt - 1);
|
|
99
|
-
await pause(Math.min(MAX_BACKOFF_MS, wait));
|
|
100
|
-
}
|
|
101
|
-
},
|
|
102
|
-
});
|
|
103
|
-
}
|
|
1
|
+
import{DEPTHS as t}from"../../search/search.js";import{tool as h}from"../../tool/index.js";const s="Searches the live web and returns what it found with the sources it rests on. Use it for anything that turns on current facts \u2014 laws, regulations, standards, prices, releases, dates, versions, who holds a post, what changed \u2014 and never answer those from memory. Given a link, reads that page.";function u(e){return e?.length?`${s} Results come only from ${e.join(", ")}; anything elsewhere is out of reach.`:s}function c(e){return e==="deep"?"Default quick, one pass, right for most lookups. pro searches wider. deep is an exhaustive review: minutes, and many times the cost; ask for it only when a wrong answer is expensive.":"Default quick, one pass, right for most lookups. Raise to pro when a first pass would not settle it. deep is not enabled here."}function f(e){if(typeof e?.search!="function")throw new Error("webSearch needs a searcher to run on: sonar({ apiKey }) or searxng({ baseUrl }).");const{search:o}=e,r=e.maxDepth??"pro";return h({name:"web_search",description:u(e.search.domains),effect:"readonly",trust:"external",args:[{name:"query",type:"string",required:!0,maxLength:1e3,description:"What to find out, as a question. One thing, not several."},{name:"depth",type:"enum",required:!1,options:["quick","pro","deep"],description:c(r)},{name:"recency",type:"enum",required:!1,options:["day","week","month","year"],description:"Only consider pages published within this. Leave unset for anything."}],run:async({query:a,depth:n="quick",recency:i})=>{if(t.indexOf(n)>t.indexOf(r))throw new Error(`web_search was asked for "${n}", above the "${r}" this deployment allows.`);return o({query:a,depth:n,recency:i})}})}export{f as webSearch};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type PoliteOptions } from "../polite.js";
|
|
2
|
+
export interface Found {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
description: string;
|
|
6
|
+
}
|
|
7
|
+
export interface Snak {
|
|
8
|
+
snaktype: string;
|
|
9
|
+
datatype?: string;
|
|
10
|
+
datavalue?: {
|
|
11
|
+
type: string;
|
|
12
|
+
value: unknown;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export interface Statement {
|
|
16
|
+
mainsnak: Snak;
|
|
17
|
+
rank: "preferred" | "normal" | "deprecated";
|
|
18
|
+
references?: {
|
|
19
|
+
snaks?: Record<string, Snak[]>;
|
|
20
|
+
}[];
|
|
21
|
+
}
|
|
22
|
+
export interface Entity {
|
|
23
|
+
id: string;
|
|
24
|
+
label: string;
|
|
25
|
+
description: string;
|
|
26
|
+
claims: Record<string, Statement[]>;
|
|
27
|
+
/** The Wikipedia article in the tool's language, when there is one. */
|
|
28
|
+
wikipedia: string | null;
|
|
29
|
+
}
|
|
30
|
+
export declare const ITEM_URL = "https://www.wikidata.org/wiki/";
|
|
31
|
+
export interface WikidataApi {
|
|
32
|
+
search(name: string): Promise<Found | null>;
|
|
33
|
+
entity(id: string): Promise<Entity | null>;
|
|
34
|
+
labels(ids: readonly string[]): Promise<Map<string, string>>;
|
|
35
|
+
}
|
|
36
|
+
export interface ApiOptions extends Omit<PoliteOptions, "userAgent"> {
|
|
37
|
+
language?: string;
|
|
38
|
+
}
|
|
39
|
+
/** The three Wikidata calls the tool makes, behind one interface a test can fake. */
|
|
40
|
+
export declare function wikidataApi(opts?: ApiOptions): WikidataApi;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{politeGet as u}from"../polite.js";const d="https://www.wikidata.org/w/api.php",f="spendgraph-tools (https://spendgraph.locusgraph.com)",g=50,y="https://www.wikidata.org/wiki/";function h(i,s){const e=i.sitelinks?.[`${s}wiki`]?.title;return e?`https://${s}.wikipedia.org/wiki/${encodeURIComponent(e.replace(/ /g,"_"))}`:null}function l(i,s){return i?.[s]?.value??""}function k(i,s){const e=[];for(let o=0;o<i.length;o+=s)e.push(i.slice(o,o+s));return e}function m(i={}){const s=u({...i,userAgent:f}),e=i.language??"en",o=async n=>{const a=new URL(d);for(const[r,c]of Object.entries({...n,format:"json"}))a.searchParams.set(r,c);const t=await s(a);if(!t.ok)throw new Error(`Wikidata answered ${t.status} to ${n.action}.`);return await t.json()};return{async search(n){const t=(await o({action:"wbsearchentities",search:n,language:e,limit:"1"})).search?.[0];return t?{id:t.id,label:t.label??t.id,description:t.description??""}:null},async entity(n){const t=(await o({action:"wbgetentities",ids:n,props:"claims|labels|descriptions|sitelinks",languages:e,sitefilter:`${e}wiki`})).entities?.[n];return!t||t.missing!==void 0?null:{id:n,label:l(t.labels,e)||n,description:l(t.descriptions,e),claims:t.claims??{},wikipedia:h(t,e)}},async labels(n){const a=new Map;for(const t of k([...new Set(n)],g)){const r=await o({action:"wbgetentities",ids:t.join("|"),props:"labels",languages:e});for(const[c,w]of Object.entries(r.entities??{})){const p=l(w.labels,e);p&&a.set(c,p)}}return a}}}export{y as ITEM_URL,m as wikidataApi};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { ApiOptions, Entity, Found, Statement, WikidataApi } from "./api.js";
|
|
2
|
+
export { wikidataApi } from "./api.js";
|
|
3
|
+
export type { Fact } from "./render.js";
|
|
4
|
+
export { factsOf, rendered, statementsOf } from "./render.js";
|
|
5
|
+
export { type WikidataOptions, wikidata } from "./wikidata.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{wikidataApi as r}from"./api.js";import{factsOf as f,rendered as o,statementsOf as i}from"./render.js";import{wikidata as m}from"./wikidata.js";export{f as factsOf,o as rendered,i as statementsOf,m as wikidata,r as wikidataApi};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Entity, type Snak, type Statement } from "./api.js";
|
|
2
|
+
export interface Fact {
|
|
3
|
+
property: string;
|
|
4
|
+
values: string[];
|
|
5
|
+
/** Reference URLs the statements cite, where the value can be checked. */
|
|
6
|
+
sources: string[];
|
|
7
|
+
}
|
|
8
|
+
/** Entity ids a snak refers to — its value, or the unit of a quantity — for one labels call. */
|
|
9
|
+
export declare function referenced(snak: Snak): string[];
|
|
10
|
+
/** The statements worth reading, in the order Wikidata lists them, deprecated ones dropped. */
|
|
11
|
+
export declare function statementsOf(entity: Entity): [string, Statement[]][];
|
|
12
|
+
export declare function factsOf(statements: readonly [string, Statement[]][], labels: ReadonlyMap<string, string>): Fact[];
|
|
13
|
+
export declare function rendered(entity: Entity, facts: readonly Fact[]): string;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{ITEM_URL as c}from"./api.js";const l=new Set(["wikibase-item","time","quantity","string","monolingualtext","url","globe-coordinate"]),d=9,p=10,f=11,s="http://www.wikidata.org/entity/",$="P854";function g(t){return(t.references??[]).flatMap(n=>(n.snaks?.[$]??[]).flatMap(r=>{const e=r.datavalue?.value;return typeof e=="string"?[e]:[]}))}function m(t){const n=t.filter(e=>e.rank!=="deprecated"),r=n.filter(e=>e.rank==="preferred");return r.length>0?r:n}function h(t){return t.snaktype==="value"&&l.has(t.datatype??"")&&t.datavalue!==void 0}function w(t){const n=t.time.replace(/^\+/,""),[r]=n.split("T"),[e="",i="",o=""]=(r??"").split("-");return t.precision>=f?`${e}-${i}-${o}`:t.precision===p?`${e}-${i}`:t.precision===d?e:`c. ${e}`}function u(t){const n=t?.id;return typeof n=="string"?n:null}function y(t){const n=t.datavalue?.value,r=u(n),e=n?.unit?.startsWith(s)?n.unit.slice(s.length):null;return[r,e].filter(i=>i!==null)}function v(t,n){const r=t.datavalue?.value;switch(t.datatype){case"wikibase-item":{const e=u(r);return e?n.get(e)??e:null}case"time":return w(r);case"quantity":{const{amount:e,unit:i}=r,o=i.startsWith(s)?i.slice(s.length):null,a=o?n.get(o)??o:"";return`${e.replace(/^\+/,"")}${a?` ${a}`:""}`}case"monolingualtext":return r.text;case"globe-coordinate":{const{latitude:e,longitude:i}=r;return`${e}, ${i}`}default:return typeof r=="string"?r:null}}function x(t){return Object.entries(t.claims).map(([n,r])=>[n,m(r).filter(e=>h(e.mainsnak))]).filter(([,n])=>n.length>0)}function M(t,n){return t.map(([r,e])=>({property:n.get(r)??r,values:e.flatMap(i=>{const o=v(i.mainsnak,n);return o?[o]:[]}),sources:[...new Set(e.flatMap(g))]}))}function k(t){const n=t.sources.length>0?` \u2014 source: ${t.sources.join(", ")}`:"";return`${t.property}: ${t.values.join(", ")}${n}`}function R(t,n){const r=`${t.label} (${t.id})${t.description?` \u2014 ${t.description}`:""}`,e=[`${c}${t.id}`,...t.wikipedia?[t.wikipedia]:[]];return[r,...n.map(k),`source: ${e.join(" \xB7 ")}`].join(`
|
|
2
|
+
`)}export{M as factsOf,y as referenced,R as rendered,x as statementsOf};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ApiOptions, type WikidataApi } from "./api.js";
|
|
2
|
+
export interface WikidataOptions extends ApiOptions {
|
|
3
|
+
/** Facts kept per thing. Default 30; a country has hundreds. */
|
|
4
|
+
maxFacts?: number;
|
|
5
|
+
/** Injected for tests, in place of the three calls to Wikidata. */
|
|
6
|
+
api?: WikidataApi;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Facts about a thing from Wikidata, as text — its label, what it is, and
|
|
10
|
+
* the statements made about it, with every id resolved to a name.
|
|
11
|
+
*
|
|
12
|
+
* Free and unauthenticated. Entity, date, number and place claims are what it
|
|
13
|
+
* settles; prices, versions and anything recent are not in it.
|
|
14
|
+
*/
|
|
15
|
+
export declare function wikidata(opts?: WikidataOptions): import("../../index.js").Tool<import("../../index.js").ArgsOf<readonly [{
|
|
16
|
+
readonly name: "question";
|
|
17
|
+
readonly type: "string";
|
|
18
|
+
readonly required: true;
|
|
19
|
+
readonly maxLength: 200;
|
|
20
|
+
readonly description: "The thing's name, optionally followed by ': ' and what about it.";
|
|
21
|
+
}]>>;
|