@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,41 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { between, parseDate, shift } from "./shift.js";
|
|
3
|
-
const UNITS = ["seconds", "minutes", "hours", "days", "weeks", "months", "years"];
|
|
4
|
-
export function dateMath() {
|
|
5
|
-
return tool({
|
|
6
|
-
name: "date_math",
|
|
7
|
-
effect: "readonly",
|
|
8
|
-
description: "Works out a date exactly. `add` and `subtract` move a date by an amount of a " +
|
|
9
|
-
"unit; `difference` says how far apart two dates are in whole units. Use it for " +
|
|
10
|
-
"deadlines, notice periods, ages and 'how long ago' — never work a date out yourself.",
|
|
11
|
-
args: [
|
|
12
|
-
{
|
|
13
|
-
name: "operation",
|
|
14
|
-
type: "enum",
|
|
15
|
-
required: true,
|
|
16
|
-
options: ["add", "subtract", "difference"],
|
|
17
|
-
},
|
|
18
|
-
{ name: "date", type: "string", required: true, description: "ISO, as in 2026-03-31." },
|
|
19
|
-
{ name: "unit", type: "enum", required: true, options: [...UNITS] },
|
|
20
|
-
{ name: "amount", type: "number", required: false, description: "For add and subtract." },
|
|
21
|
-
{
|
|
22
|
-
name: "to",
|
|
23
|
-
type: "string",
|
|
24
|
-
required: false,
|
|
25
|
-
description: "The other date, for difference.",
|
|
26
|
-
},
|
|
27
|
-
],
|
|
28
|
-
run: ({ operation, date, unit, amount, to }) => {
|
|
29
|
-
const from = parseDate(date, "date");
|
|
30
|
-
if (operation === "difference") {
|
|
31
|
-
if (!to)
|
|
32
|
-
throw new Error("difference needs a second date in `to`.");
|
|
33
|
-
return { unit, difference: between(from, parseDate(to, "to"), unit) };
|
|
34
|
-
}
|
|
35
|
-
if (amount === undefined)
|
|
36
|
-
throw new Error(`${operation} needs an \`amount\`.`);
|
|
37
|
-
const moved = shift(from, operation === "subtract" ? -amount : amount, unit);
|
|
38
|
-
return { iso: moved.toISOString(), date: moved.toISOString().slice(0, 10) };
|
|
39
|
-
},
|
|
40
|
-
});
|
|
41
|
-
}
|
|
1
|
+
import{tool as i}from"../../tool/index.js";import{between as u,parseDate as d,shift as f}from"./shift.js";const y=["seconds","minutes","hours","days","weeks","months","years","weekdays"],c=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];function h(){return i({name:"date_math",effect:"readonly",description:"Works out a date exactly. `add` and `subtract` move a date by an amount of a unit; `difference` says how far apart two dates are in whole units. `weekdays` is Monday to Friday, for anything written in working days; it knows nothing about holidays. Use it for deadlines, notice periods, ages and 'how long ago' \u2014 never work a date out yourself.",args:[{name:"operation",type:"enum",required:!0,options:["add","subtract","difference"]},{name:"date",type:"string",required:!0,description:"ISO, as in 2026-03-31."},{name:"unit",type:"enum",required:!0,options:[...y]},{name:"amount",type:"number",required:!1,description:"For add and subtract."},{name:"to",type:"string",required:!1,description:"The other date, for difference."}],run:({operation:e,date:s,unit:t,amount:r,to:n})=>{const o=d(s,"date");if(e==="difference"){if(!n)throw new Error("difference needs a second date in `to`.");return{unit:t,difference:u(o,d(n,"to"),t)}}if(r===void 0)throw new Error(`${e} needs an \`amount\`.`);const a=f(o,e==="subtract"?-r:r,t);return{iso:a.toISOString(),date:a.toISOString().slice(0,10),weekday:c[a.getUTCDay()]}}})}export{h as dateMath};
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { between, parseDate, shift } from "./shift.js";
|
|
1
|
+
import{dateMath as r}from"./date.js";import{between as a,parseDate as f,shift as p}from"./shift.js";import{dates as s}from"./words.js";export{a as between,r as dateMath,s as dates,f as parseDate,p as shift};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type Unit = "seconds" | "minutes" | "hours" | "days" | "weeks" | "months" | "years";
|
|
1
|
+
export type Unit = "seconds" | "minutes" | "hours" | "days" | "weeks" | "months" | "years" | "weekdays";
|
|
2
2
|
/** Parses an ISO instant, or says which argument was not one. */
|
|
3
3
|
export declare function parseDate(value: string, label: string): Date;
|
|
4
4
|
/** Moves an instant by a whole number of units. Negative goes backwards. */
|
|
@@ -1,47 +1 @@
|
|
|
1
|
-
const
|
|
2
|
-
seconds: 1000,
|
|
3
|
-
minutes: 60_000,
|
|
4
|
-
hours: 3_600_000,
|
|
5
|
-
days: 86_400_000,
|
|
6
|
-
weeks: 604_800_000,
|
|
7
|
-
};
|
|
8
|
-
export function parseDate(value, label) {
|
|
9
|
-
const at = new Date(value);
|
|
10
|
-
if (!Number.isFinite(at.getTime())) {
|
|
11
|
-
throw new Error(`${label} "${value}" is not a date. Use an ISO instant, as in 2026-03-31.`);
|
|
12
|
-
}
|
|
13
|
-
return at;
|
|
14
|
-
}
|
|
15
|
-
function addMonths(from, months) {
|
|
16
|
-
const day = from.getUTCDate();
|
|
17
|
-
const shifted = new Date(from.getTime());
|
|
18
|
-
shifted.setUTCDate(1);
|
|
19
|
-
shifted.setUTCMonth(shifted.getUTCMonth() + months);
|
|
20
|
-
const lastOfMonth = new Date(Date.UTC(shifted.getUTCFullYear(), shifted.getUTCMonth() + 1, 0)).getUTCDate();
|
|
21
|
-
shifted.setUTCDate(Math.min(day, lastOfMonth));
|
|
22
|
-
return shifted;
|
|
23
|
-
}
|
|
24
|
-
export function shift(from, amount, unit) {
|
|
25
|
-
if (!Number.isInteger(amount))
|
|
26
|
-
throw new Error("amount must be a whole number.");
|
|
27
|
-
if (unit === "months")
|
|
28
|
-
return addMonths(from, amount);
|
|
29
|
-
if (unit === "years")
|
|
30
|
-
return addMonths(from, amount * 12);
|
|
31
|
-
const ms = MS[unit];
|
|
32
|
-
if (!ms)
|
|
33
|
-
throw new Error(`"${unit}" is not a unit this tool knows.`);
|
|
34
|
-
return new Date(from.getTime() + amount * ms);
|
|
35
|
-
}
|
|
36
|
-
export function between(from, to, unit) {
|
|
37
|
-
if (unit === "months" || unit === "years") {
|
|
38
|
-
const months = (to.getUTCFullYear() - from.getUTCFullYear()) * 12 +
|
|
39
|
-
(to.getUTCMonth() - from.getUTCMonth()) -
|
|
40
|
-
(to.getUTCDate() < from.getUTCDate() ? 1 : 0);
|
|
41
|
-
return unit === "years" ? Math.trunc(months / 12) : months;
|
|
42
|
-
}
|
|
43
|
-
const ms = MS[unit];
|
|
44
|
-
if (!ms)
|
|
45
|
-
throw new Error(`"${unit}" is not a unit this tool knows.`);
|
|
46
|
-
return Math.trunc((to.getTime() - from.getTime()) / ms);
|
|
47
|
-
}
|
|
1
|
+
const T={seconds:1e3,minutes:6e4,hours:36e5,days:864e5,weeks:6048e5};function D(e,t){const n=new Date(e);if(!Number.isFinite(n.getTime()))throw new Error(`${t} "${e}" is not a date. Use an ISO instant, as in 2026-03-31.`);return n}function w(e,t){const n=e.getUTCDate(),s=new Date(e.getTime());s.setUTCDate(1),s.setUTCMonth(s.getUTCMonth()+t);const a=new Date(Date.UTC(s.getUTCFullYear(),s.getUTCMonth()+1,0)).getUTCDate();return s.setUTCDate(Math.min(n,a)),s}const i=864e5,g=e=>e.getUTCDay()===0||e.getUTCDay()===6;function h(e,t){const n=t<0?-i:i;let s=new Date(e.getTime()),a=Math.abs(t);for(;a>0;)s=new Date(s.getTime()+n),g(s)||(a-=1);return s}function l(e,t,n){if(!Number.isInteger(t))throw new Error("amount must be a whole number.");if(n==="months")return w(e,t);if(n==="years")return w(e,t*12);if(n==="weekdays")return h(e,t);const s=T[n];if(!s)throw new Error(`"${n}" is not a unit this tool knows.`);return new Date(e.getTime()+t*s)}function U(e,t,n){if(n==="months"||n==="years"){const a=(t.getUTCFullYear()-e.getUTCFullYear())*12+(t.getUTCMonth()-e.getUTCMonth())-(t.getUTCDate()<e.getUTCDate()?1:0);return n==="years"?Math.trunc(a/12):a}if(n==="weekdays")return c(e,t);const s=T[n];if(!s)throw new Error(`"${n}" is not a unit this tool knows.`);return Math.trunc((t.getTime()-e.getTime())/s)}function c(e,t){const n=t.getTime()<e.getTime(),[s,a]=n?[t,e]:[e,t];let o=0,r=new Date(s.getTime());for(;r.getTime()+i<=a.getTime();)r=new Date(r.getTime()+i),g(r)||(o+=1);return n?-o:o}export{U as between,D as parseDate,l as shift};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `date_math` asked in words, for a caller that can only send a question.
|
|
3
|
+
*
|
|
4
|
+
* Three forms are read — a span between two dates, and a date moved forward
|
|
5
|
+
* or back — with dates as ISO. Anything else is refused with the forms named,
|
|
6
|
+
* so a miss is recorded rather than a date guessed at.
|
|
7
|
+
*/
|
|
8
|
+
export declare function dates(): import("../../index.js").Tool<import("../../index.js").ArgsOf<readonly [{
|
|
9
|
+
readonly name: "question";
|
|
10
|
+
readonly type: "string";
|
|
11
|
+
readonly required: true;
|
|
12
|
+
readonly maxLength: 2000;
|
|
13
|
+
}]>>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{tool as f}from"../../tool/index.js";import{dateMath as h}from"./date.js";const a=String.raw`(\d{4}-\d{2}-\d{2}(?:T[\d:.]+Z?)?)`,m=String.raw`(-?\d+(?:\.\d+)?)`,r=String.raw`(seconds?|minutes?|hours?|days?|weeks?|months?|years?)`,w=new RegExp(`${r}\\s+(?:from|between)\\s+${a}\\s+(?:to|and)\\s+${a}`,"i"),p=new RegExp(`${m}\\s+${r}\\s+after\\s+${a}`,"i"),E=new RegExp(`${m}\\s+${r}\\s+before\\s+${a}`,"i"),u="'days from 2026-03-01 to 2026-09-09', '30 days after 2026-03-01', or '2 months before 2026-03-01'",i=t=>(t.endsWith("s")?t:`${t}s`).toLowerCase();function $(t){const o=t.match(w);if(o){const[,e,n,s]=o;return{operation:"difference",unit:i(e),date:n,to:s}}const d=t.match(p);if(d){const[,e,n,s]=d;return{operation:"add",amount:Number(e),unit:i(n),date:s}}const c=t.match(E);if(c){const[,e,n,s]=c;return{operation:"subtract",amount:Number(e),unit:i(n),date:s}}throw new Error(`Ask date_math as one of ${u}, with dates as YYYY-MM-DD.`)}function b(){const t=h();return f({name:"date_math",description:`Date arithmetic, exact: how far apart two dates are, or a date moved by an amount. Ask as ${u}. Units: seconds, minutes, hours, days, weeks, months, years.`,effect:"readonly",args:[{name:"question",type:"string",required:!0,maxLength:2e3}],run:({question:o})=>t.run($(o))})}export{b as dates};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ToolResult } from "../../types/index.js";
|
|
2
|
+
/** Why an answer was not given. */
|
|
3
|
+
export type Unanswerable = "unknowable" | "not_covered" | "out_of_scope" | "unsafe";
|
|
4
|
+
/** What `decline` returns, so a loop can tell it from an answer. */
|
|
5
|
+
export interface Declined {
|
|
6
|
+
declined: true;
|
|
7
|
+
because: Unanswerable;
|
|
8
|
+
reason: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* True when this result is the model saying it will not answer.
|
|
12
|
+
*
|
|
13
|
+
* The counterpart to `isFinished`. Both end a run and they are opposite
|
|
14
|
+
* outcomes, so a caller that reads only "the loop finished" cannot tell a
|
|
15
|
+
* question answered from a question refused — and a refusal recorded as an
|
|
16
|
+
* answer is a support queue that looks healthy and a customer who got nothing.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isDeclined(result: ToolResult): boolean;
|
|
19
|
+
/** Reads the refusal out of a result, or null if it is not one. */
|
|
20
|
+
export declare function declinedFor(result: ToolResult): Declined | null;
|
|
21
|
+
/**
|
|
22
|
+
* Say the question cannot be answered, rather than answering it anyway.
|
|
23
|
+
*
|
|
24
|
+
* The third thing an agent can do, beside answering and asking. Asking only
|
|
25
|
+
* helps where the missing piece is the person's to supply: "which subscription"
|
|
26
|
+
* has an answer they know, and "what will the base rate be next March" has no
|
|
27
|
+
* answer anybody knows. A model with only `finish` and a clarifying question
|
|
28
|
+
* handles the second by guessing or by asking something that cannot help.
|
|
29
|
+
*
|
|
30
|
+
* `because` is a small fixed set rather than free text, so the rate of each is
|
|
31
|
+
* countable. `unknowable` and `not_covered` want opposite fixes — one is the
|
|
32
|
+
* world, the other is your records — and a single "declined" number cannot tell
|
|
33
|
+
* you which you have.
|
|
34
|
+
*
|
|
35
|
+
* Pinned for the reason `finish` is: it is how a run ends, not a tool competing
|
|
36
|
+
* to be relevant, and a turn whose shortlist dropped it has no way to say no.
|
|
37
|
+
*/
|
|
38
|
+
export declare function decline(): import("../../index.js").Tool<import("../../index.js").ArgsOf<readonly [{
|
|
39
|
+
readonly name: "because";
|
|
40
|
+
readonly type: "enum";
|
|
41
|
+
readonly required: true;
|
|
42
|
+
readonly options: Unanswerable[];
|
|
43
|
+
}, {
|
|
44
|
+
readonly name: "reason";
|
|
45
|
+
readonly type: "text";
|
|
46
|
+
readonly required: true;
|
|
47
|
+
readonly maxLength: 2000;
|
|
48
|
+
readonly description: "One sentence the person who asked can read.";
|
|
49
|
+
}]>>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{tool as t}from"../../tool/index.js";const o=["unknowable","not_covered","out_of_scope","unsafe"];function r(e){if(e.name!=="decline"||e.status!=="completed")return!1;try{return JSON.parse(e.output).declined===!0}catch{return!1}}function u(e){return r(e)?JSON.parse(e.output):null}function i(){return t({name:"decline",effect:"readonly",pinned:!0,description:"Call this when the question cannot be answered: nobody knows (unknowable), your records do not cover it (not_covered), it is not what you are for (out_of_scope), or answering would be unsafe. Use it instead of guessing. If the person could supply what is missing, ask them instead.",args:[{name:"because",type:"enum",required:!0,options:o},{name:"reason",type:"text",required:!0,maxLength:2e3,description:"One sentence the person who asked can read."}],run:({because:e,reason:n})=>({declined:!0,because:e,reason:n})})}export{i as decline,u as declinedFor,r as isDeclined};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { type Declined, decline, declinedFor, isDeclined, type Unanswerable } from "./decline.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{decline as i,declinedFor as c,isDeclined as l}from"./decline.js";export{i as decline,c as declinedFor,l as isDeclined};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ToolResult } from "../../types/index.js";
|
|
1
2
|
export interface EscalateOptions {
|
|
2
3
|
/**
|
|
3
4
|
* What actually happens — page someone, open a ticket, write to a queue.
|
|
@@ -19,6 +20,24 @@ export interface EscalationRequest {
|
|
|
19
20
|
summary?: string;
|
|
20
21
|
urgency: "low" | "normal" | "high";
|
|
21
22
|
}
|
|
23
|
+
/** What `escalate` returns, so a run can be read back for whether a person was told. */
|
|
24
|
+
export interface Escalated {
|
|
25
|
+
escalated: true;
|
|
26
|
+
/** False when nothing was wired to receive it, or the handler was not reached. */
|
|
27
|
+
delivered: boolean;
|
|
28
|
+
request: EscalationRequest;
|
|
29
|
+
handled?: unknown;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* True when this result is the model handing the work to a person.
|
|
33
|
+
*
|
|
34
|
+
* The counterpart to `isFinished`. A run that called `escalate` and a run that
|
|
35
|
+
* escalated are not the same thing — the call can fail, and telling a customer
|
|
36
|
+
* they have been passed on when nobody was told is the worse half of the two.
|
|
37
|
+
*/
|
|
38
|
+
export declare function isEscalated(result: ToolResult): boolean;
|
|
39
|
+
/** Reads the request out of an escalated result, or null if it is not one. */
|
|
40
|
+
export declare function escalationOf(result: ToolResult): Escalated | null;
|
|
22
41
|
/**
|
|
23
42
|
* Hand back to a person rather than guess.
|
|
24
43
|
*
|
|
@@ -1,20 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
export function escalate(opts = {}) {
|
|
3
|
-
return tool({
|
|
4
|
-
name: "escalate",
|
|
5
|
-
description: "Hand this over to a person. Call it when you are missing something you cannot " +
|
|
6
|
-
"get, when the request needs an authority you do not have, or when acting on a " +
|
|
7
|
-
"guess would be worse than waiting. Say plainly why.",
|
|
8
|
-
pinned: opts.pinned ?? true,
|
|
9
|
-
args: [
|
|
10
|
-
{ name: "reason", type: "text", required: true, maxLength: 2000 },
|
|
11
|
-
{ name: "summary", type: "text", required: false, maxLength: 4000 },
|
|
12
|
-
{ name: "urgency", type: "enum", required: false, options: ["low", "normal", "high"] },
|
|
13
|
-
],
|
|
14
|
-
run: async ({ reason, summary, urgency = "normal" }) => {
|
|
15
|
-
const request = { reason, summary, urgency };
|
|
16
|
-
const handled = opts.onEscalate ? await opts.onEscalate(request) : undefined;
|
|
17
|
-
return { escalated: true, delivered: Boolean(opts.onEscalate), request, handled };
|
|
18
|
-
},
|
|
19
|
-
});
|
|
20
|
-
}
|
|
1
|
+
import{tool as u}from"../../tool/index.js";function s(e){if(e.name!=="escalate"||e.status!=="completed")return!1;try{return JSON.parse(e.output).escalated===!0}catch{return!1}}function l(e){return s(e)?JSON.parse(e.output):null}function c(e={}){return u({name:"escalate",description:"Hand this over to a person. Call it when you are missing something you cannot get, when the request needs an authority you do not have, or when acting on a guess would be worse than waiting. Say plainly why.",pinned:e.pinned??!0,args:[{name:"reason",type:"text",required:!0,maxLength:2e3},{name:"summary",type:"text",required:!1,maxLength:4e3},{name:"urgency",type:"enum",required:!1,options:["low","normal","high"]}],run:async({reason:t,summary:a,urgency:r="normal"})=>{const n={reason:t,summary:a,urgency:r},o=e.onEscalate?await e.onEscalate(n):void 0;return{escalated:!0,delivered:!!e.onEscalate,request:n,handled:o}}})}export{c as escalate,l as escalationOf,s as isEscalated};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { type EscalateOptions, type EscalationRequest, escalate } from "./escalate.js";
|
|
1
|
+
export { type Escalated, type EscalateOptions, type EscalationRequest, escalate, escalationOf, isEscalated, } from "./escalate.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
import{escalate as s,escalationOf as t,isEscalated as c}from"./escalate.js";export{s as escalate,t as escalationOf,c as isEscalated};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface ExtractOptions {
|
|
2
|
+
/** Hosts a file may be read from. Default any public https host. */
|
|
3
|
+
allow?: string[];
|
|
4
|
+
/** Longest text handed back. Default 20000 characters. */
|
|
5
|
+
maxChars?: number;
|
|
6
|
+
/** Largest file read. Default 20 MB. */
|
|
7
|
+
maxBytes?: number;
|
|
8
|
+
/** Give up after this. Default 20000. */
|
|
9
|
+
timeoutMs?: number;
|
|
10
|
+
/** Injected for tests. */
|
|
11
|
+
fetch?: typeof fetch;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The text of a file at a URL — a PDF, a Word document, a page, or plain
|
|
15
|
+
* text — so a model can read what a link points at instead of guessing.
|
|
16
|
+
*
|
|
17
|
+
* The same guard as `http_request`: https only, no private addresses, and an
|
|
18
|
+
* allowlist you choose. Nothing is stored.
|
|
19
|
+
*/
|
|
20
|
+
export declare function extract(opts?: ExtractOptions): import("../../index.js").Tool<import("../../index.js").ArgsOf<readonly [{
|
|
21
|
+
readonly name: "url";
|
|
22
|
+
readonly type: "string";
|
|
23
|
+
readonly required: true;
|
|
24
|
+
readonly maxLength: 2000;
|
|
25
|
+
}]>>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{tool as u}from"../../tool/index.js";import{ANY_HOST as w,checkUrl as y}from"../http/allow.js";import{followChecked as g}from"../http/hops.js";import{KINDS as x,kindOf as b,nameOf as l}from"./kinds.js";import{read as E}from"./readers.js";const T=2e4,_=20*1024*1024,p=2e4;function M(t={}){const o=t.allow?.length?t.allow:[w],m=t.fetch??globalThis.fetch.bind(globalThis),i=t.maxChars??T,s=t.maxBytes??_;return u({name:"extract",effect:"readonly",trust:"external",description:"Reads the file at a URL and returns its text: a PDF, a Word document (.docx), a web page, Markdown or plain text. Use it when a finding or a person hands you a link and you need what is actually on it, not a summary of it.",args:[{name:"url",type:"string",required:!0,maxLength:2e3}],run:async({url:d})=>{const e=y(d,o),h=AbortSignal.timeout(t.timeoutMs??p),r=await g(f=>m(f,{signal:h,redirect:"manual"}),e,o);if(!r.ok)throw new Error(`${e.hostname} answered ${r.status}.`);const c=b(r.headers.get("content-type"),e);if(!c)throw new Error(`Cannot read ${l(e)}: not one of ${x.join(", ")}, by type or by name.`);const n=await r.arrayBuffer();if(n.byteLength>s)throw new Error(`${l(e)} is ${n.byteLength} bytes, over the ${s} read.`);const a=await E(c,n);return a.length>i?`${a.slice(0,i).trimEnd()}\u2026`:a}})}export{M as extract};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{extract as f}from"./extract.js";import{KINDS as e,kindOf as t,nameOf as x}from"./kinds.js";import{fromDocx as d,fromHtml as p,fromPdf as c,read as n}from"./readers.js";export{e as KINDS,f as extract,d as fromDocx,p as fromHtml,c as fromPdf,t as kindOf,x as nameOf,n as read};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const KINDS: readonly ["pdf", "docx", "html", "md", "txt"];
|
|
2
|
+
export type Kind = (typeof KINDS)[number];
|
|
3
|
+
/** What a file is: its declared type first, the URL's extension when the type says nothing. */
|
|
4
|
+
export declare function kindOf(contentType: string | null, url: URL): Kind | null;
|
|
5
|
+
/** The name the file goes by, from the last path segment, or the host when there is none. */
|
|
6
|
+
export declare function nameOf(url: URL): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const a=["pdf","docx","html","md","txt"],p=[["application/pdf","pdf"],["application/vnd.openxmlformats-officedocument.wordprocessingml.document","docx"],["text/html","html"],["application/xhtml+xml","html"],["text/markdown","md"],["text/x-markdown","md"],["text/plain","txt"]],l={pdf:"pdf",docx:"docx",html:"html",htm:"html",md:"md",markdown:"md",txt:"txt",text:"txt"};function c(t,o){const e=(t??"").split(";")[0]?.trim().toLowerCase()??"",n=p.find(([d])=>d===e)?.[1];if(n)return n;const m=o.pathname.split(".").pop()?.toLowerCase()??"";return l[m]??null}function x(t){const o=t.pathname.split("/").filter(Boolean).pop();return o?decodeURIComponent(o):t.hostname}export{a as KINDS,c as kindOf,x as nameOf};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Kind } from "./kinds.js";
|
|
2
|
+
/** The words in a page: scripts and styles gone, block ends become breaks, tags dropped. */
|
|
3
|
+
export declare function fromHtml(html: string): string;
|
|
4
|
+
/** The words in a Word document: each paragraph in `word/document.xml`, in order. */
|
|
5
|
+
export declare function fromDocx(bytes: ArrayBuffer): Promise<string>;
|
|
6
|
+
export declare function fromPdf(bytes: ArrayBuffer): Promise<string>;
|
|
7
|
+
export declare function read(kind: Kind, bytes: ArrayBuffer): Promise<string>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import p from"jszip";import{extractText as m}from"unpdf";const u={amp:"&",lt:"<",gt:">",quot:'"',apos:"'",nbsp:" "};function o(t){return t.replace(/&(#\d+|#x[0-9a-f]+|[a-z]+);/gi,(r,e)=>e.startsWith("#x")?String.fromCodePoint(Number.parseInt(e.slice(2),16)):e.startsWith("#")?String.fromCodePoint(Number(e.slice(1))):u[e.toLowerCase()]??r)}function n(t){return t.replace(/[ \t]+\n/g,`
|
|
2
|
+
`).replace(/\n{3,}/g,`
|
|
3
|
+
|
|
4
|
+
`).trim()}function l(t){const r=t.replace(/<(script|style)\b[\s\S]*?<\/\1>/gi,"").replace(/<br\s*\/?>/gi,`
|
|
5
|
+
`).replace(/<\/(p|div|h[1-6]|li|tr|blockquote|pre|section|article|header|footer)>/gi,`
|
|
6
|
+
|
|
7
|
+
`).replace(/<[^>]+>/g,"");return n(o(r))}async function d(t){const e=(await p.loadAsync(t)).file("word/document.xml");if(!e)throw new Error("This is not a Word document: it has no word/document.xml.");const c=[...(await e.async("string")).matchAll(/<w:p\b[\s\S]*?<\/w:p>/g)].map(i=>[...i[0].matchAll(/<w:t\b[^>]*>([\s\S]*?)<\/w:t>/g)].map(s=>s[1]??"").join(""));return n(c.map(o).join(`
|
|
8
|
+
|
|
9
|
+
`))}async function f(t){const{text:r}=await m(new Uint8Array(t),{mergePages:!0});return n(r)}const a=new TextDecoder;async function h(t,r){switch(t){case"pdf":return f(r);case"docx":return d(r);case"html":return l(a.decode(r));default:return n(a.decode(r))}}export{d as fromDocx,l as fromHtml,f as fromPdf,h as read};
|
|
@@ -18,6 +18,10 @@ export declare function answerOf(result: ToolResult): string | null;
|
|
|
18
18
|
*
|
|
19
19
|
* Without it a loop ends by the model going quiet, which is indistinguishable
|
|
20
20
|
* from it losing the thread.
|
|
21
|
+
*
|
|
22
|
+
* Pinned, so a shortlist cannot drop it. It is not a tool competing to be
|
|
23
|
+
* relevant to the question — it is how the loop is told to stop, and a turn
|
|
24
|
+
* offered four tools that are not this one has no way to say it is done.
|
|
21
25
|
*/
|
|
22
26
|
export declare function finish(): import("../../index.js").Tool<import("../../index.js").ArgsOf<readonly [{
|
|
23
27
|
readonly name: "answer";
|
|
@@ -1,26 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
export function isFinished(result) {
|
|
3
|
-
if (result.name !== "finish" || result.status !== "completed")
|
|
4
|
-
return false;
|
|
5
|
-
try {
|
|
6
|
-
return JSON.parse(result.output).done === true;
|
|
7
|
-
}
|
|
8
|
-
catch {
|
|
9
|
-
return false;
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
export function answerOf(result) {
|
|
13
|
-
if (!isFinished(result))
|
|
14
|
-
return null;
|
|
15
|
-
return JSON.parse(result.output).answer;
|
|
16
|
-
}
|
|
17
|
-
export function finish() {
|
|
18
|
-
return tool({
|
|
19
|
-
name: "finish",
|
|
20
|
-
effect: "readonly",
|
|
21
|
-
description: "Call this when you have the answer and need no more tools. Pass the final " +
|
|
22
|
-
"answer as it should be given to the person who asked.",
|
|
23
|
-
args: [{ name: "answer", type: "text", required: true, maxLength: 20_000 }],
|
|
24
|
-
run: ({ answer }) => ({ done: true, answer }),
|
|
25
|
-
});
|
|
26
|
-
}
|
|
1
|
+
import{tool as n}from"../../tool/index.js";function t(e){if(e.name!=="finish"||e.status!=="completed")return!1;try{return JSON.parse(e.output).done===!0}catch{return!1}}function o(e){return t(e)?JSON.parse(e.output).answer:null}function a(){return n({name:"finish",effect:"readonly",pinned:!0,description:"Call this when you have the answer and need no more tools. Pass the final answer as it should be given to the person who asked.",args:[{name:"answer",type:"text",required:!0,maxLength:2e4}],run:({answer:e})=>({done:!0,answer:e})})}export{o as answerOf,a as finish,t as isFinished};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
import{answerOf as e,finish as f,isFinished as n}from"./finish.js";export{e as answerOf,f as finish,n as isFinished};
|
|
@@ -1,37 +1 @@
|
|
|
1
|
-
const
|
|
2
|
-
const LOCAL_NAMES = new Set(["localhost", "metadata.google.internal"]);
|
|
3
|
-
function isPrivateHost(host) {
|
|
4
|
-
const name = host.toLowerCase();
|
|
5
|
-
if (LOCAL_NAMES.has(name))
|
|
6
|
-
return true;
|
|
7
|
-
if (name.endsWith(".local") || name.endsWith(".internal") || name.endsWith(".localhost")) {
|
|
8
|
-
return true;
|
|
9
|
-
}
|
|
10
|
-
if (name.startsWith("["))
|
|
11
|
-
return true;
|
|
12
|
-
return PRIVATE_V4.test(name);
|
|
13
|
-
}
|
|
14
|
-
function matches(host, rule) {
|
|
15
|
-
const h = host.toLowerCase();
|
|
16
|
-
const r = rule.toLowerCase();
|
|
17
|
-
return r.startsWith(".") ? h === r.slice(1) || h.endsWith(r) : h === r;
|
|
18
|
-
}
|
|
19
|
-
export function checkUrl(raw, allow) {
|
|
20
|
-
let url;
|
|
21
|
-
try {
|
|
22
|
-
url = new URL(raw);
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
throw new Error(`"${raw}" is not a URL.`);
|
|
26
|
-
}
|
|
27
|
-
if (url.protocol !== "https:") {
|
|
28
|
-
throw new Error(`Only https is allowed, and this is ${url.protocol.replace(":", "")}.`);
|
|
29
|
-
}
|
|
30
|
-
if (isPrivateHost(url.hostname)) {
|
|
31
|
-
throw new Error(`${url.hostname} is a private or link-local address.`);
|
|
32
|
-
}
|
|
33
|
-
if (!allow.some((rule) => matches(url.hostname, rule))) {
|
|
34
|
-
throw new Error(`${url.hostname} is not in the allowlist: ${allow.join(", ")}.`);
|
|
35
|
-
}
|
|
36
|
-
return url;
|
|
37
|
-
}
|
|
1
|
+
const n=/^(10\.|127\.|0\.|169\.254\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.|100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\.)/,s=new Set(["localhost","metadata.google.internal"]);function i(e){const t=e.toLowerCase();return s.has(t)||t.endsWith(".local")||t.endsWith(".internal")||t.endsWith(".localhost")||t.startsWith("[")?!0:n.test(t)}const a="*";function h(e,t){if(t===a)return!0;const r=e.toLowerCase(),o=t.toLowerCase();return o.startsWith(".")?r===o.slice(1)||r.endsWith(o):r===o}function c(e,t){let r;try{r=new URL(e)}catch{throw new Error(`"${e}" is not a URL.`)}if(r.protocol!=="https:")throw new Error(`Only https is allowed, and this is ${r.protocol.replace(":","")}.`);if(i(r.hostname))throw new Error(`${r.hostname} is a private or link-local address.`);if(!t.some(o=>h(r.hostname,o)))throw new Error(`${r.hostname} is not in the allowlist: ${t.join(", ")}.`);return r}export{a as ANY_HOST,c as checkUrl};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The response, with every hop checked against the allowlist rather than only
|
|
3
|
+
* the URL the model named.
|
|
4
|
+
*
|
|
5
|
+
* Followed automatically, an allowlisted host answering `302 Location: …` sends
|
|
6
|
+
* the request anywhere it likes — a private address, a metadata endpoint — and
|
|
7
|
+
* carries any configured header with it. `send` must therefore pass
|
|
8
|
+
* `redirect: "manual"`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function followChecked(send: (to: URL) => Promise<Response>, target: URL, allow: string[]): Promise<Response>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{checkUrl as s}from"./allow.js";const a=3,c=e=>e.status>=300&&e.status<400;async function l(e,o,i){let t=await e(o);for(let r=0;r<a&&c(t);r++){const n=t.headers.get("location");if(!n)break;t=await e(s(new URL(n,t.url||o).toString(),i))}if(c(t))throw new Error(`Gave up after ${a} redirects from ${o.hostname}.`);return t}export{l as followChecked};
|
|
@@ -3,7 +3,8 @@ export interface HttpOptions {
|
|
|
3
3
|
* Hosts this tool may reach. Required, and there is no default.
|
|
4
4
|
*
|
|
5
5
|
* `"api.example.com"` matches that host; `".example.com"` matches it and any
|
|
6
|
-
* subdomain
|
|
6
|
+
* subdomain; `"*"` matches every public host, and is the choice to make only
|
|
7
|
+
* when the tool is a reader of the open web rather than a client of your APIs.
|
|
7
8
|
*/
|
|
8
9
|
allow: string[];
|
|
9
10
|
/** Sent on every request — an API key belongs here, never in the args. */
|
|
@@ -14,6 +15,8 @@ export interface HttpOptions {
|
|
|
14
15
|
maxChars?: number;
|
|
15
16
|
/** Injected for tests. */
|
|
16
17
|
fetch?: typeof fetch;
|
|
18
|
+
/** Injected for tests, for reading a `Retry-After` given as a date. */
|
|
19
|
+
now?: () => number;
|
|
17
20
|
}
|
|
18
21
|
/**
|
|
19
22
|
* An HTTP GET or POST, to hosts you named.
|
|
@@ -24,6 +27,11 @@ export interface HttpOptions {
|
|
|
24
27
|
*
|
|
25
28
|
* Its `effect` is left unset on purpose: a GET is readonly and a POST to your
|
|
26
29
|
* allowlist may be anything at all, and one label cannot be true of both.
|
|
30
|
+
*
|
|
31
|
+
* It makes one request and never retries. Retrying is the caller's, because
|
|
32
|
+
* only the caller knows whether the call is safe to repeat — and the one thing
|
|
33
|
+
* a caller needs to retry well, `retryAfterMs`, is handed back rather than
|
|
34
|
+
* discarded with the rest of the headers.
|
|
27
35
|
*/
|
|
28
36
|
export declare function httpRequest(opts: HttpOptions): import("../../index.js").Tool<import("../../index.js").ArgsOf<readonly [{
|
|
29
37
|
readonly name: "url";
|
|
@@ -1,41 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { checkUrl } from "./allow.js";
|
|
3
|
-
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
4
|
-
const DEFAULT_MAX_CHARS = 100_000;
|
|
5
|
-
export function httpRequest(opts) {
|
|
6
|
-
if (!opts.allow?.length) {
|
|
7
|
-
throw new Error("httpRequest needs an allow list of hosts; there is no safe default.");
|
|
8
|
-
}
|
|
9
|
-
const doFetch = opts.fetch ?? globalThis.fetch.bind(globalThis);
|
|
10
|
-
const maxChars = opts.maxChars ?? DEFAULT_MAX_CHARS;
|
|
11
|
-
return tool({
|
|
12
|
-
name: "http_request",
|
|
13
|
-
description: `Fetches a URL and returns its status and body. Only these hosts are reachable: ` +
|
|
14
|
-
`${opts.allow.join(", ")}. Use it to read an API or a page you have been pointed at.`,
|
|
15
|
-
args: [
|
|
16
|
-
{ name: "url", type: "string", required: true, maxLength: 2000 },
|
|
17
|
-
{ name: "method", type: "enum", required: false, options: ["GET", "POST"] },
|
|
18
|
-
{ name: "body", type: "text", required: false, maxLength: 20_000 },
|
|
19
|
-
],
|
|
20
|
-
run: async ({ url, method = "GET", body }) => {
|
|
21
|
-
const target = checkUrl(url, opts.allow);
|
|
22
|
-
const signal = AbortSignal.timeout(opts.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
23
|
-
const res = await doFetch(target, {
|
|
24
|
-
method,
|
|
25
|
-
signal,
|
|
26
|
-
headers: {
|
|
27
|
-
...opts.headers,
|
|
28
|
-
...(body ? { "content-type": "application/json" } : {}),
|
|
29
|
-
},
|
|
30
|
-
...(body && method === "POST" ? { body } : {}),
|
|
31
|
-
});
|
|
32
|
-
const text = await res.text();
|
|
33
|
-
return {
|
|
34
|
-
status: res.status,
|
|
35
|
-
ok: res.ok,
|
|
36
|
-
body: text.slice(0, maxChars),
|
|
37
|
-
truncated: text.length > maxChars,
|
|
38
|
-
};
|
|
39
|
-
},
|
|
40
|
-
});
|
|
41
|
-
}
|
|
1
|
+
import{tool as d}from"../../tool/index.js";import{ANY_HOST as m,checkUrl as f}from"./allow.js";import{followChecked as g}from"./hops.js";import{retryAfterMs as p}from"./retry-after.js";const y=1e4,w=1e5;function T(e){return e.includes(m)?"Any public https host is reachable.":`Only these hosts are reachable: ${e.join(", ")}.`}function q(e){if(!e.allow?.length)throw new Error("httpRequest needs an allow list of hosts; there is no safe default.");const i=e.fetch??globalThis.fetch.bind(globalThis),a=e.maxChars??w;return d({name:"http_request",description:`Fetches a URL and returns its status and body. ${T(e.allow)} Use it to read an API or a page you have been pointed at.`,args:[{name:"url",type:"string",required:!0,maxLength:2e3},{name:"method",type:"enum",required:!1,options:["GET","POST"]},{name:"body",type:"text",required:!1,maxLength:2e4}],run:async({url:l,method:n="GET",body:r})=>{const c=f(l,e.allow),h=AbortSignal.timeout(e.timeoutMs??y),t=await g(u=>i(u,{method:n,signal:h,redirect:"manual",headers:{...e.headers,...r?{"content-type":"application/json"}:{}},...r&&n==="POST"?{body:r}:{}}),c,e.allow),s=await t.text(),o=p(t.headers.get("retry-after"),e.now);return{status:t.status,ok:t.ok,body:s.slice(0,a),truncated:s.length>a,...o===null?{}:{retryAfterMs:o}}}})}export{q as httpRequest};
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { httpRequest } from "./http.js";
|
|
1
|
+
import{checkUrl as o}from"./allow.js";import{followChecked as f}from"./hops.js";import{httpRequest as m}from"./http.js";import{retryAfterMs as c}from"./retry-after.js";export{o as checkUrl,f as followChecked,m as httpRequest,c as retryAfterMs};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How long a server asked you to wait, in milliseconds, or null.
|
|
3
|
+
*
|
|
4
|
+
* `Retry-After` is the one part of a 429 or a 503 that is not guesswork: the
|
|
5
|
+
* server knows when its window resets and says so. Dropping it leaves a caller
|
|
6
|
+
* backing off by a schedule it invented, which is either too slow or — the
|
|
7
|
+
* expensive one — too fast, and a retry inside the window spends a request to
|
|
8
|
+
* be told to wait again.
|
|
9
|
+
*
|
|
10
|
+
* Two forms, both in the spec: whole seconds, or an HTTP date. A date in the
|
|
11
|
+
* past is zero rather than negative, because "wait -4 seconds" is not something
|
|
12
|
+
* a caller should have to guard against.
|
|
13
|
+
*/
|
|
14
|
+
export declare function retryAfterMs(header: string | null, now?: () => number): number | null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function u(r,n=Date.now){if(!r)return null;const t=r.trim();if(/^\d+$/.test(t))return Number(t)*1e3;const e=Date.parse(t);return Number.isNaN(e)?null:Math.max(0,e-n())}export{u as retryAfterMs};
|
package/dist/builtin/index.d.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
export { calculate, evaluate } from "./calculate/index.js";
|
|
2
2
|
export { type ClockOptions, currentTime } from "./clock/index.js";
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
5
|
-
export { type
|
|
3
|
+
export { type CodeExecOptions, codeExec, type Sandbox, type SandboxRequest, type SandboxResult, } from "./code-exec/index.js";
|
|
4
|
+
export { dateMath, dates } from "./date/index.js";
|
|
5
|
+
export { type Declined, decline, declinedFor, isDeclined, type Unanswerable, } from "./decline/index.js";
|
|
6
|
+
export { type Escalated, type EscalateOptions, type EscalationRequest, escalate, escalationOf, isEscalated, } from "./escalate/index.js";
|
|
7
|
+
export { type ExtractOptions, extract } from "./extract/index.js";
|
|
6
8
|
export { answerOf, type Finished, finish, isFinished } from "./finish/index.js";
|
|
7
|
-
export { checkUrl, type HttpOptions, httpRequest } from "./http/index.js";
|
|
8
|
-
export { type JsonQueryOptions, jsonQuery } from "./json/index.js";
|
|
9
|
+
export { checkUrl, type HttpOptions, httpRequest, retryAfterMs } from "./http/index.js";
|
|
10
|
+
export { type JsonQueryOptions, jsonFrom, jsonQuery } from "./json/index.js";
|
|
11
|
+
export { type NotesOptions, type NotesStore, notes, notesStore } from "./notes/index.js";
|
|
12
|
+
export { type OpenAlexApi, type OpenAlexOptions, openalex } from "./openalex/index.js";
|
|
13
|
+
export { type Publisher, type PublishOptions, publish } from "./publish/index.js";
|
|
9
14
|
export { think } from "./think/index.js";
|
|
10
15
|
export { type SearchDepth, type WebSearchOptions, webSearch } from "./web-search/index.js";
|
|
11
|
-
export { type
|
|
16
|
+
export { type WikidataApi, type WikidataOptions, wikidata } from "./wikidata/index.js";
|
package/dist/builtin/index.js
CHANGED
|
@@ -1,11 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { currentTime } from "./clock/index.js";
|
|
3
|
-
export { dateMath } from "./date/index.js";
|
|
4
|
-
export { deepRecall } from "./deep-recall/index.js";
|
|
5
|
-
export { escalate } from "./escalate/index.js";
|
|
6
|
-
export { answerOf, finish, isFinished } from "./finish/index.js";
|
|
7
|
-
export { checkUrl, httpRequest } from "./http/index.js";
|
|
8
|
-
export { jsonQuery } from "./json/index.js";
|
|
9
|
-
export { think } from "./think/index.js";
|
|
10
|
-
export { webSearch } from "./web-search/index.js";
|
|
11
|
-
export { CommissionAbortedError, CommissionTimeoutError, DEFAULT_DESIGN, mergeDesign, renderDesign, writeDocument, } from "./write-document/index.js";
|
|
1
|
+
import{calculate as o,evaluate as t}from"./calculate/index.js";import{currentTime as a}from"./clock/index.js";import{codeExec as x}from"./code-exec/index.js";import{dateMath as s,dates as i}from"./date/index.js";import{decline as n,declinedFor as l,isDeclined as d}from"./decline/index.js";import{escalate as u,escalationOf as k,isEscalated as w}from"./escalate/index.js";import{extract as b}from"./extract/index.js";import{answerOf as y,finish as E,isFinished as M}from"./finish/index.js";import{checkUrl as S,httpRequest as q,retryAfterMs as v}from"./http/index.js";import{jsonFrom as D,jsonQuery as Q}from"./json/index.js";import{notes as T,notesStore as U}from"./notes/index.js";import{openalex as z}from"./openalex/index.js";import{publish as C}from"./publish/index.js";import{think as H}from"./think/index.js";import{webSearch as J}from"./web-search/index.js";import{wikidata as L}from"./wikidata/index.js";export{y as answerOf,o as calculate,S as checkUrl,x as codeExec,a as currentTime,s as dateMath,i as dates,n as decline,l as declinedFor,u as escalate,k as escalationOf,t as evaluate,b as extract,E as finish,q as httpRequest,d as isDeclined,w as isEscalated,M as isFinished,D as jsonFrom,Q as jsonQuery,T as notes,U as notesStore,z as openalex,C as publish,v as retryAfterMs,H as think,J as webSearch,L as wikidata};
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { readPath } from "./path.js";
|
|
1
|
+
import{jsonQuery as e}from"./json.js";import{jsonFrom as t}from"./parse.js";import{readPath as p}from"./path.js";export{t as jsonFrom,e as jsonQuery,p as readPath};
|