@spendgraph/tools 0.6.0 → 0.8.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/README.md +3 -2
- package/dist/builtin/calculate/calculate.d.ts +2 -0
- package/dist/builtin/calculate/calculate.js +1 -1
- package/dist/builtin/calculate/index.d.ts +2 -1
- package/dist/builtin/calculate/index.js +1 -1
- package/dist/builtin/code-exec/code-exec.d.ts +14 -0
- package/dist/builtin/date/date.d.ts +1 -1
- package/dist/builtin/date/date.js +1 -1
- package/dist/builtin/date/index.d.ts +1 -0
- package/dist/builtin/date/index.js +1 -1
- package/dist/builtin/date/shift.d.ts +1 -1
- package/dist/builtin/date/shift.js +1 -1
- 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 -1
- 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 -1
- package/dist/builtin/http/allow.d.ts +2 -0
- package/dist/builtin/http/allow.js +1 -1
- 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 -1
- package/dist/builtin/http/index.d.ts +2 -0
- package/dist/builtin/http/index.js +1 -1
- 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 -1
- package/dist/builtin/json/index.d.ts +1 -0
- package/dist/builtin/json/index.js +1 -1
- package/dist/builtin/json/json.js +1 -1
- package/dist/builtin/json/parse.d.ts +15 -0
- package/dist/builtin/json/parse.js +1 -0
- 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 +26 -0
- package/dist/builtin/openalex/api.js +1 -0
- package/dist/builtin/openalex/index.d.ts +3 -0
- package/dist/builtin/openalex/index.js +1 -0
- package/dist/builtin/openalex/openalex.d.ts +36 -0
- package/dist/builtin/openalex/openalex.js +1 -0
- package/dist/builtin/paper-search/index.d.ts +1 -0
- package/dist/builtin/paper-search/index.js +1 -0
- package/dist/builtin/paper-search/paper-search.d.ts +38 -0
- package/dist/builtin/paper-search/paper-search.js +3 -0
- package/dist/builtin/polite.d.ts +15 -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/web-search/web-search.d.ts +10 -35
- package/dist/builtin/web-search/web-search.js +1 -1
- 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 +23 -0
- package/dist/builtin/wikidata/wikidata.js +1 -0
- package/dist/bus/bus.d.ts +23 -1
- package/dist/bus/bus.js +1 -1
- package/dist/bus/group.d.ts +14 -1
- package/dist/bus/group.js +1 -1
- package/dist/bus/index.d.ts +4 -1
- package/dist/bus/index.js +1 -1
- package/dist/bus/invoke.d.ts +1 -1
- package/dist/bus/invoke.js +3 -1
- 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/options.d.ts +31 -0
- package/dist/bus/output.d.ts +0 -17
- package/dist/bus/output.js +2 -2
- package/dist/bus/select.d.ts +0 -16
- package/dist/bus/select.js +1 -1
- package/dist/bus/spent.d.ts +16 -0
- package/dist/bus/spent.js +1 -0
- package/dist/bus/trace.d.ts +14 -1
- package/dist/bus/trace.js +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.js +1 -1
- package/dist/internals.d.ts +8 -1
- package/dist/internals.js +1 -1
- 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/{builtin → memories}/deep-recall/deep-recall.d.ts +3 -0
- package/dist/memories/deep-recall/deep-recall.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/papers/consensus/consensus.d.ts +12 -0
- package/dist/papers/consensus/consensus.js +1 -0
- package/dist/papers/consensus/index.d.ts +4 -0
- package/dist/papers/consensus/index.js +1 -0
- package/dist/papers/consensus/reply.d.ts +24 -0
- package/dist/papers/consensus/reply.js +1 -0
- package/dist/papers/every.d.ts +6 -0
- package/dist/papers/every.js +1 -0
- package/dist/papers/index.d.ts +11 -0
- package/dist/papers/index.js +1 -0
- package/dist/papers/openalex/index.d.ts +2 -0
- package/dist/papers/openalex/index.js +1 -0
- package/dist/papers/openalex/openalex.d.ts +8 -0
- package/dist/papers/openalex/openalex.js +1 -0
- package/dist/papers/papers.d.ts +54 -0
- package/dist/papers/papers.js +1 -0
- package/dist/papers/render.d.ts +2 -0
- package/dist/papers/render.js +4 -0
- package/dist/papers/semantic-scholar/index.d.ts +4 -0
- package/dist/papers/semantic-scholar/index.js +1 -0
- package/dist/papers/semantic-scholar/reply.d.ts +29 -0
- package/dist/papers/semantic-scholar/reply.js +1 -0
- package/dist/papers/semantic-scholar/semantic-scholar.d.ts +8 -0
- package/dist/papers/semantic-scholar/semantic-scholar.js +1 -0
- package/dist/search/every.d.ts +6 -0
- package/dist/search/every.js +4 -0
- package/dist/search/index.d.ts +7 -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 +69 -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/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/tool.d.ts +8 -1
- package/dist/tool/tool.js +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/result.d.ts +38 -0
- package/dist/types/tool.d.ts +74 -0
- package/docs/bogus.mdx +85 -0
- package/docs/builtins.mdx +217 -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 -9
- package/dist/builtin/deep-recall/deep-recall.js +0 -1
- package/dist/builtin/write-document/commission.d.ts +0 -56
- package/dist/builtin/write-document/commission.js +0 -13
- package/dist/builtin/write-document/design.d.ts +0 -49
- package/dist/builtin/write-document/design.js +0 -5
- package/dist/builtin/write-document/http.d.ts +0 -34
- package/dist/builtin/write-document/http.js +0 -1
- package/dist/builtin/write-document/index.d.ts +0 -6
- package/dist/builtin/write-document/index.js +0 -1
- package/dist/builtin/write-document/types.d.ts +0 -33
- package/dist/builtin/write-document/write-document.d.ts +0 -69
- package/dist/builtin/write-document/write-document.js +0 -1
- /package/dist/{builtin/write-document/types.js → bus/only.js} +0 -0
- /package/dist/{builtin → memories}/deep-recall/index.d.ts +0 -0
- /package/dist/{builtin → memories}/deep-recall/index.js +0 -0
- /package/dist/{builtin/web-search → search/sonar}/reply.d.ts +0 -0
- /package/dist/{builtin/web-search → search/sonar}/reply.js +0 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type PaperFilters } from "../../papers/papers.js";
|
|
2
|
+
import { type PoliteOptions } from "../polite.js";
|
|
3
|
+
export interface Paper {
|
|
4
|
+
title: string;
|
|
5
|
+
year: number | null;
|
|
6
|
+
authors: string[];
|
|
7
|
+
venue: string | null;
|
|
8
|
+
citations: number;
|
|
9
|
+
doi: string | null;
|
|
10
|
+
pdf: string | null;
|
|
11
|
+
abstract: string;
|
|
12
|
+
}
|
|
13
|
+
export interface Works {
|
|
14
|
+
papers: Paper[];
|
|
15
|
+
costMicroUsd?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface OpenAlexApi {
|
|
18
|
+
search(query: string, limit: number, filters?: PaperFilters): Promise<Works>;
|
|
19
|
+
}
|
|
20
|
+
export interface ApiOptions extends Omit<PoliteOptions, "userAgent" | "headers"> {
|
|
21
|
+
apiKey?: string;
|
|
22
|
+
}
|
|
23
|
+
/** OpenAlex stores an abstract as word → positions; this puts the words back in order. */
|
|
24
|
+
export declare function abstractOf(index: Record<string, number[]> | null | undefined): string;
|
|
25
|
+
/** The one OpenAlex call the tool makes, behind an interface a test can fake. */
|
|
26
|
+
export declare function openAlexApi(opts?: ApiOptions): OpenAlexApi;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{microUsd as u}from"../../papers/papers.js";import{politeGet as p}from"../polite.js";const d="https://api.openalex.org/works",f="spendgraph-tools (https://spendgraph.locusgraph.com)",_=/[?*]/g;function h(e){return e.replace(_," ").replace(/\s+/g," ").trim()}const m=["id","doi","title","publication_year","cited_by_count","authorships","primary_location","open_access","abstract_inverted_index"].join(",");function y(e){if(!e)return"";const t=[];for(const[n,s]of Object.entries(e))for(const o of s)t[o]=n;return t.filter(n=>n!==void 0).join(" ")}function b(e){return{title:e.title?.trim()||"(untitled)",year:e.publication_year??null,authors:(e.authorships??[]).flatMap(t=>t.author?.display_name?[t.author.display_name]:[]),venue:e.primary_location?.source?.display_name||null,citations:e.cited_by_count??0,doi:e.doi?.replace(/^https?:\/\/doi\.org\//,"")||null,pdf:e.open_access?.oa_url||e.primary_location?.pdf_url||null,abstract:y(e.abstract_inverted_index)}}function g(e){if(!e)return null;const{yearMin:t,yearMax:n,openAccess:s,minCitations:o}=e,r=[t===void 0?null:`publication_year:>${t-1}`,n===void 0?null:`publication_year:<${n+1}`,o===void 0?null:`cited_by_count:>${o-1}`,s?"is_oa:true":null].filter(a=>a!==null);return r.length>0?r.join(","):null}function j(e={}){const t=p({...e,userAgent:f,...e.apiKey?{headers:{authorization:`Bearer ${e.apiKey}`}}:{}});return{async search(n,s,o){const r=new URL(d);r.searchParams.set("search",h(n)),r.searchParams.set("per-page",String(s)),r.searchParams.set("select",m);const a=g(o);a&&r.searchParams.set("filter",a);const i=await t(r);if(!i.ok)throw new Error(`OpenAlex answered ${i.status}.`);const c=await i.json(),l=u(c.meta?.cost_usd);return{papers:(c.results??[]).map(b),...l===void 0?{}:{costMicroUsd:l}}}}}export{y as abstractOf,j as openAlexApi};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{abstractOf as p,openAlexApi as r}from"./api.js";import{openalex as x}from"./openalex.js";export{p as abstractOf,r as openAlexApi,x as openalex};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type OpenAlexPapersOptions } from "../../papers/openalex/index.js";
|
|
2
|
+
export interface OpenAlexOptions extends OpenAlexPapersOptions {
|
|
3
|
+
maxAbstractChars?: number;
|
|
4
|
+
}
|
|
5
|
+
export declare function openalex(opts?: OpenAlexOptions): import("../../index.js").Tool<import("../../index.js").ArgsOf<readonly [{
|
|
6
|
+
readonly name: "query";
|
|
7
|
+
readonly type: "string";
|
|
8
|
+
readonly required: true;
|
|
9
|
+
readonly maxLength: 1000;
|
|
10
|
+
readonly description: string;
|
|
11
|
+
}, {
|
|
12
|
+
readonly name: "year_min";
|
|
13
|
+
readonly type: "number";
|
|
14
|
+
readonly required: false;
|
|
15
|
+
readonly min: 1500;
|
|
16
|
+
readonly max: 2200;
|
|
17
|
+
readonly description: "Earliest year of publication. Leave unset for any.";
|
|
18
|
+
}, {
|
|
19
|
+
readonly name: "year_max";
|
|
20
|
+
readonly type: "number";
|
|
21
|
+
readonly required: false;
|
|
22
|
+
readonly min: 1500;
|
|
23
|
+
readonly max: 2200;
|
|
24
|
+
readonly description: "Latest year of publication. Leave unset for any.";
|
|
25
|
+
}, {
|
|
26
|
+
readonly name: "min_citations";
|
|
27
|
+
readonly type: "number";
|
|
28
|
+
readonly required: false;
|
|
29
|
+
readonly min: 0;
|
|
30
|
+
readonly description: "Only papers cited at least this often. Raises confidence and hides recent work.";
|
|
31
|
+
}, {
|
|
32
|
+
readonly name: "open_access";
|
|
33
|
+
readonly type: "boolean";
|
|
34
|
+
readonly required: false;
|
|
35
|
+
readonly description: "Only papers whose full text can be read without a subscription.";
|
|
36
|
+
}]>>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{openAlexPapers as e}from"../../papers/openalex/index.js";import{paperSearch as t}from"../paper-search/index.js";const s=5,o=400;function n(r={}){const a=r.maxResults??s;return t({papers:e({...r,maxResults:a}),maxResults:a,maxAbstractChars:r.maxAbstractChars??o})}export{n as openalex};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { type PaperSearchOptions, paperSearch } from "./paper-search.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{paperSearch as p}from"./paper-search.js";export{p as paperSearch};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type PaperSearcher } from "../../papers/papers.js";
|
|
2
|
+
export interface PaperSearchOptions {
|
|
3
|
+
papers: PaperSearcher;
|
|
4
|
+
maxResults?: number;
|
|
5
|
+
maxAbstractChars?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function paperSearch(opts: PaperSearchOptions): import("../../index.js").Tool<import("../../index.js").ArgsOf<readonly [{
|
|
8
|
+
readonly name: "query";
|
|
9
|
+
readonly type: "string";
|
|
10
|
+
readonly required: true;
|
|
11
|
+
readonly maxLength: 1000;
|
|
12
|
+
readonly description: string;
|
|
13
|
+
}, {
|
|
14
|
+
readonly name: "year_min";
|
|
15
|
+
readonly type: "number";
|
|
16
|
+
readonly required: false;
|
|
17
|
+
readonly min: 1500;
|
|
18
|
+
readonly max: 2200;
|
|
19
|
+
readonly description: "Earliest year of publication. Leave unset for any.";
|
|
20
|
+
}, {
|
|
21
|
+
readonly name: "year_max";
|
|
22
|
+
readonly type: "number";
|
|
23
|
+
readonly required: false;
|
|
24
|
+
readonly min: 1500;
|
|
25
|
+
readonly max: 2200;
|
|
26
|
+
readonly description: "Latest year of publication. Leave unset for any.";
|
|
27
|
+
}, {
|
|
28
|
+
readonly name: "min_citations";
|
|
29
|
+
readonly type: "number";
|
|
30
|
+
readonly required: false;
|
|
31
|
+
readonly min: 0;
|
|
32
|
+
readonly description: "Only papers cited at least this often. Raises confidence and hides recent work.";
|
|
33
|
+
}, {
|
|
34
|
+
readonly name: "open_access";
|
|
35
|
+
readonly type: "boolean";
|
|
36
|
+
readonly required: false;
|
|
37
|
+
readonly description: "Only papers whose full text can be read without a subscription.";
|
|
38
|
+
}]>>;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{DEFAULT_ABSTRACT_CHARS as m,DEFAULT_MAX_PAPERS as f}from"../../papers/papers.js";import{rendered as w}from"../../papers/render.js";import{tool as y}from"../../tool/index.js";const i="Finds published papers on a topic and returns them as text: title, authors, venue, citation count, DOI, an open PDF when there is one, how the study was run where that is known, 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.";function b(e){const t=e.pdf??e.url??(e.doi?`https://doi.org/${e.doi}`:null);return t?{url:t,title:e.title,...e.year===null?{}:{date:String(e.year)}}:null}function x(e){return e?`${i} Papers come from ${e}.`:i}function _(e){if(typeof e?.papers!="function")throw new Error("paperSearch needs a paper searcher to run on: consensus({ apiKey }), semanticScholar() or openAlexPapers().");const{papers:t}=e,c=e.maxResults??f,u=e.maxAbstractChars??m;return y({name:"paper_search",description:x(t.source),effect:"readonly",trust:"external",wants:{name:"query",description:"the same ask as a short search phrase \u2014 the words a paper title or abstract would carry, not a sentence. A whole question finds far less."},costOf:s=>s.costMicroUsd,args:[{name:"query",type:"string",required:!0,maxLength:1e3,description:"The topic, as a phrase a paper title or abstract would contain. A whole question works and is ranked, but the shorter the phrase the better it matches."},{name:"year_min",type:"number",required:!1,min:1500,max:2200,description:"Earliest year of publication. Leave unset for any."},{name:"year_max",type:"number",required:!1,min:1500,max:2200,description:"Latest year of publication. Leave unset for any."},{name:"min_citations",type:"number",required:!1,min:0,description:"Only papers cited at least this often. Raises confidence and hides recent work."},{name:"open_access",type:"boolean",required:!1,description:"Only papers whose full text can be read without a subscription."}],run:async({query:s,year_min:p,year_max:d,min_citations:h,open_access:l})=>{const n=s.trim();if(!n)throw new Error("Name the topic to search for.");const r=await t({query:n,limit:c,yearMin:p,yearMax:d,minCitations:h,openAccess:l}),o=(r.failed??[]).map(a=>`${a.source} did not answer: ${a.reason}`);if(r.papers.length===0)throw new Error([`No papers matching "${n}" in ${r.source}.`,...o].join(" "));return{answer:[w(n,r.papers,u),...o].join(`
|
|
2
|
+
|
|
3
|
+
`),results:r.papers.map(b).filter(a=>a!==null),...r.costMicroUsd===void 0?{}:{costMicroUsd:r.costMicroUsd}}}})}export{_ as paperSearch};
|
|
@@ -0,0 +1,15 @@
|
|
|
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
|
+
headers?: Record<string, string>;
|
|
5
|
+
timeoutMs?: number;
|
|
6
|
+
/** Injected for tests. */
|
|
7
|
+
fetch?: typeof fetch;
|
|
8
|
+
/** Injected for tests, in place of sleeping out a Retry-After. */
|
|
9
|
+
wait?: (ms: number) => Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A GET that behaves the way the free, keyless APIs ask: named, timed out,
|
|
13
|
+
* and waiting out a 429 for as long as Retry-After says before asking again.
|
|
14
|
+
*/
|
|
15
|
+
export declare function politeGet(opts: PoliteOptions): (url: URL) => Promise<Response>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const E=429,_=2,c=1e3,R=5e3,u=1e4,a=t=>new Promise(e=>setTimeout(e,t));function o(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??a,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,...t.headers}});if(s.status!==429||r>=2)return s;await n(o(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 ("pdf" | "html" | "md" | "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,47 +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, and the
|
|
27
|
-
* wait is spread by up to one backoff on top: a whole wave limited at the
|
|
28
|
-
* same instant computes the same delay, and retrying in step rebuilds the
|
|
29
|
-
* burst that caused it.
|
|
30
|
-
*/
|
|
31
|
-
attempts?: number;
|
|
32
|
-
/** Injected for tests. */
|
|
33
|
-
fetch?: typeof fetch;
|
|
34
|
-
/** Injected for tests. */
|
|
35
|
-
sleep?: (ms: number) => Promise<void>;
|
|
36
|
-
/** Injected for tests. Returns 0 to 1, and spreads a retried wave apart. */
|
|
37
|
-
random?: () => number;
|
|
38
13
|
}
|
|
39
14
|
/**
|
|
40
|
-
* The live web, through
|
|
15
|
+
* The live web, through the searcher it is built on.
|
|
41
16
|
*
|
|
42
|
-
* The tier is the model's to choose and yours to cap:
|
|
43
|
-
* minutes and orders of magnitude, and a model reaching for it unprompted
|
|
44
|
-
* 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
|
|
45
20
|
* price, because what a search costs is the pricing table's business.
|
|
46
21
|
*/
|
|
47
22
|
export declare function webSearch(opts: WebSearchOptions): import("../../index.js").Tool<import("../../index.js").ArgsOf<readonly [{
|
|
@@ -55,7 +30,7 @@ export declare function webSearch(opts: WebSearchOptions): import("../../index.j
|
|
|
55
30
|
readonly type: "enum";
|
|
56
31
|
readonly required: false;
|
|
57
32
|
readonly options: readonly ["quick", "pro", "deep"];
|
|
58
|
-
readonly description:
|
|
33
|
+
readonly description: string;
|
|
59
34
|
}, {
|
|
60
35
|
readonly name: "recency";
|
|
61
36
|
readonly type: "enum";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
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,23 @@
|
|
|
1
|
+
import { type ApiOptions, type WikidataApi } from "./api.js";
|
|
2
|
+
import { type Fact } from "./render.js";
|
|
3
|
+
export interface WikidataOptions extends ApiOptions {
|
|
4
|
+
/** Facts kept per thing. Default 30; a country has hundreds. */
|
|
5
|
+
maxFacts?: number;
|
|
6
|
+
/** Injected for tests, in place of the three calls to Wikidata. */
|
|
7
|
+
api?: WikidataApi;
|
|
8
|
+
}
|
|
9
|
+
export declare function matching(facts: readonly Fact[], about: string): Fact[];
|
|
10
|
+
/**
|
|
11
|
+
* Facts about a thing from Wikidata, as text — its label, what it is, and
|
|
12
|
+
* the statements made about it, with every id resolved to a name.
|
|
13
|
+
*
|
|
14
|
+
* Free and unauthenticated. Entity, date, number and place claims are what it
|
|
15
|
+
* settles; prices, versions and anything recent are not in it.
|
|
16
|
+
*/
|
|
17
|
+
export declare function wikidata(opts?: WikidataOptions): import("../../index.js").Tool<import("../../index.js").ArgsOf<readonly [{
|
|
18
|
+
readonly name: "question";
|
|
19
|
+
readonly type: "string";
|
|
20
|
+
readonly required: true;
|
|
21
|
+
readonly maxLength: 200;
|
|
22
|
+
readonly description: string;
|
|
23
|
+
}]>>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{tool as y}from"../../tool/index.js";import{wikidataApi as b}from"./api.js";import{factsOf as g,referenced as k,rendered as p,statementsOf as x}from"./render.js";const v=30;function A(e){const t=e.indexOf(":");if(t<1)return{name:e.trim(),about:null};const o=e.slice(t+1).trim();return{name:e.slice(0,t).trim(),about:o||null}}const L={founded:["inception","start time"],founder:["founded by"],created:["inception"],started:["inception"],began:["inception"],born:["date of birth"],died:["date of death"],headquarters:["headquarters location"],ceo:["chief executive officer"],boss:["chief executive officer"],owner:["owned by"],parent:["parent organization"]};function N(e,t){const s=t.toLowerCase().split(/\s+/).filter(Boolean).flatMap(n=>[n,...L[n]??[]]);return e.filter(n=>{const a=n.property.toLowerCase();return s.some(r=>a.includes(r))})}function F(e={}){const t=e.api??b(e),o=e.maxFacts??v;return y({name:"wikidata",effect:"readonly",trust:"external",description:"One curated fact about one named thing, with the source it came from. It settles who, when, where and what-is: who makes or runs something, when it began or a person was born, what a place's capital or population is, what a compound's formula is. Name the thing \u2014 a company, person, place, product, substance or work \u2014 and add ': ' and the one property wanted, as in 'Cloudflare: inception' or 'Ozempic: manufacturer'. Not for what a study found, which is a paper and not a property; not for a price or anything contested, which has no single recorded value; and not for anything recent, because a volunteer updates it when they get round to it.",wants:{name:"entity",description:"the thing's name on its own \u2014 'Novo Nordisk', never a sentence or a question \u2014 optionally followed by ': ' and the one property wanted, as in 'Novo Nordisk: inception'."},args:[{name:"question",type:"string",required:!0,maxLength:200,description:"The thing's name on its own, optionally followed by ': ' and the property wanted. A question finds nothing \u2014 Wikidata is searched by name."}],run:async({question:s})=>{const{name:n,about:a}=A(s);if(!n)throw new Error("Name the thing to look up.");const r=await t.search(n);if(!r)throw new Error(`Wikidata has nothing named "${n}".`);const i=await t.entity(r.id);if(!i)throw new Error(`Wikidata has no record for ${r.id}.`);const c=x(i),f=c.flatMap(([l,w])=>[l,...w.flatMap(m=>k(m.mainsnak))]),u=await t.labels(f),d=g(c,u);if(!a)return p(i,d.slice(0,o));const h=N(d,a);if(h.length===0)throw new Error(`Wikidata records nothing about "${a}" for ${i.label}.`);return p(i,h.slice(0,o))}})}export{N as matching,F as wikidata};
|
package/dist/bus/bus.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { RolloutStep } from "@spendgraph/sdk";
|
|
2
|
-
import type { Effect, Tool, ToolDeclaration, ToolResult } from "../types/index.js";
|
|
2
|
+
import type { Effect, Tool, ToolDeclaration, ToolResult, Trust } from "../types/index.js";
|
|
3
3
|
import { type AnthropicTool, type OpenAITool } from "../wire/index.js";
|
|
4
|
+
import type { Offering } from "./only.js";
|
|
4
5
|
import type { ToolBusOptions } from "./options.js";
|
|
5
6
|
import { type ToolTrace } from "./trace.js";
|
|
6
7
|
/**
|
|
@@ -32,6 +33,8 @@ export declare function toolbus(tools: Tool<never>[], opts?: ToolBusOptions): {
|
|
|
32
33
|
* `readonly` — nothing has claimed that.
|
|
33
34
|
*/
|
|
34
35
|
effectOf: (name: string) => Effect | undefined;
|
|
36
|
+
/** What a tool declared about where its content comes from. Unset means unknown. */
|
|
37
|
+
trustOf: (name: string) => Trust | undefined;
|
|
35
38
|
/**
|
|
36
39
|
* Names of the tools declared `destructive`.
|
|
37
40
|
*
|
|
@@ -72,5 +75,24 @@ export declare function toolbus(tools: Tool<never>[], opts?: ToolBusOptions): {
|
|
|
72
75
|
step: (result: ToolResult, index: number) => RolloutStep;
|
|
73
76
|
/** One turn's tool use, selected once and recorded for the rollout. */
|
|
74
77
|
trace: (query?: string, limit?: number) => ToolTrace;
|
|
78
|
+
/**
|
|
79
|
+
* The same bus, offering only these tools.
|
|
80
|
+
*
|
|
81
|
+
* What progressive disclosure needs: a model is shown the handful this turn
|
|
82
|
+
* is about while everything else stays reachable. Declaring fifty tools
|
|
83
|
+
* costs their whole schema on every call before anyone has said anything,
|
|
84
|
+
* and the cost is not only tokens — a shortlist is chosen from more
|
|
85
|
+
* accurately than a shelf.
|
|
86
|
+
*
|
|
87
|
+
* A view rather than a second bus. `invoke` still goes through this one, so
|
|
88
|
+
* usage, the output cap and a once-each memory stay the ones the caller
|
|
89
|
+
* configured — a `toolbus` rebuilt over a filtered list looks identical and
|
|
90
|
+
* quietly keeps its own, including a fresh record of which destructive
|
|
91
|
+
* calls have already run.
|
|
92
|
+
*
|
|
93
|
+
* Names it does not have are ignored, the way selection ignores a term it
|
|
94
|
+
* cannot match.
|
|
95
|
+
*/
|
|
96
|
+
only: (names: string[]) => Offering;
|
|
75
97
|
};
|
|
76
98
|
export type ToolBus = ReturnType<typeof toolbus>;
|
package/dist/bus/bus.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{toAnthropic as
|
|
1
|
+
import{toAnthropic as g,toMarkdown as h,toOpenAI as O}from"../wire/index.js";import{invokeTool as M}from"./invoke.js";import{onceEach as x}from"./once.js";import{warnOnOverlap as A}from"./overlap.js";import{scorerFor as F}from"./score.js";import{selectTools as k}from"./select.js";import{toStep as j}from"./step.js";import{ceilingFor as u}from"./terms.js";import{newTrace as b}from"./trace.js";function G(y,i={}){const E=i.now??(()=>Date.now()),d=u(i.limit,6),o=new Map;for(const e of y){if(o.has(e.name))throw new Error(`Two tools are called "${e.name}".`);o.set(e.name,e)}const f=new Map;(i.warnOnOverlap??!0)&&A([...o.values()]);const w=F([...o.values()]),v=i.onceEach?x():{earlier:()=>{},keep:()=>{}},c=e=>({name:e.name,description:e.description,args:e.args,...e.aliases?.length?{aliases:e.aliases}:{},...e.effect?{effect:e.effect}:{}}),m=()=>[...o.values()].map(c),l={all:()=>[...o.values()],missing:e=>e.filter(t=>!o.has(t)),get:e=>o.get(e),effectOf:e=>o.get(e)?.effect,trustOf:e=>o.get(e)?.trust,destructive:()=>[...o.values()].filter(e=>e.effect==="destructive").map(e=>e.name),unannotated:()=>[...o.values()].filter(e=>e.effect===void 0).map(e=>e.name),declarations:e=>e?e.map(t=>o.get(t)).filter(Boolean).map(t=>c(t)):m(),anthropic:e=>g(e??m()),openai:e=>O(e??m()),markdown:(e,t)=>h(e??m(),t),select:(e="",t)=>k([...o.values()],f,c,w,e,u(t,d)),invoke:async(e,t)=>{const n=v.earlier(o.get(e),e,t);if(n)return{...n,repeated:!0};const s=await M(o,f,E,e,t,i.maxOutputChars);return v.keep(o.get(e),s),s},usage:()=>Object.fromEntries(f),step:(e,t)=>j(e,t),trace:(e="",t)=>b(l.select(e,t),(n,s)=>l.invoke(n,s)),only:e=>{const t=e.map(r=>o.get(r)).filter(r=>r!==void 0),n=()=>t.map(c),s={...l,all:()=>[...t],missing:r=>r.filter(a=>!t.some(p=>p.name===a)),declarations:r=>r?n().filter(a=>r.includes(a.name)):n(),anthropic:r=>g(r??n()),openai:r=>O(r??n()),markdown:(r,a)=>h(r??n(),a),select:(r="",a)=>k(t,f,c,w,r,u(a,d)),trace:(r="",a)=>b(s.select(r,a),(p,T)=>l.invoke(p,T))};return s}};return l}export{G as toolbus};
|
package/dist/bus/group.d.ts
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import type { ToolBus } from "./bus.js";
|
|
2
|
+
import { type Namespacing } from "./namespace.js";
|
|
2
3
|
import type { ToolBusOptions } from "./options.js";
|
|
3
4
|
export interface BusMember {
|
|
4
5
|
name: string;
|
|
5
6
|
description: string;
|
|
6
7
|
bus: ToolBus;
|
|
7
8
|
}
|
|
8
|
-
export
|
|
9
|
+
export interface BusGroupOptions extends ToolBusOptions {
|
|
10
|
+
/**
|
|
11
|
+
* Name every tool after the bus that runs it — `billing__search`.
|
|
12
|
+
*
|
|
13
|
+
* Without it two buses offering the same tool name is refused, on the
|
|
14
|
+
* grounds that a name has to say which one runs. That is the right default
|
|
15
|
+
* for tools you wrote and the wrong one for servers you did not: `search` is
|
|
16
|
+
* the commonest tool name there is, and a group that will not start is worse
|
|
17
|
+
* than one that disambiguates. Pass a function to name them yourself.
|
|
18
|
+
*/
|
|
19
|
+
namespace?: Namespacing;
|
|
20
|
+
}
|
|
21
|
+
export declare function busgroup(members: BusMember[], opts?: BusGroupOptions): ToolBus;
|
package/dist/bus/group.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{toAnthropic as
|
|
1
|
+
import{toAnthropic as T,toMarkdown as b,toOpenAI as E}from"../wire/index.js";import{naming as N}from"./namespace.js";import{scorerFor as I}from"./score.js";import{toStep as S}from"./step.js";import{ceilingFor as $,termsIn as A}from"./terms.js";import{newTrace as j}from"./trace.js";const C=6,x=3;function L(r){return{name:r.name,description:r.description,args:[]}}function G(r,f){const a=new Map;for(const o of r)for(const c of o.bus.all()){const l=f.offered(o,c.name),w=a.get(l);if(w)throw new Error(`Two buses offer a tool called "${l}": "${w.name}" and "${o.name}". A name has to say which one runs \u2014 set \`namespace\` to name them after their bus.`);a.set(l,o)}return a}function U(r,f){return r.flatMap(a=>{const o=a.bus.all().filter(c=>c.pinned).map(c=>c.name);return o.length>0?v(a,f,a.bus.declarations(o)):[]})}const v=(r,f,a)=>a.map(o=>({...o,name:f.offered(r,o.name)}));function _(r){const f=[],a=new Set;let o=r;return{chosen:f,holds:c=>a.has(c),full:()=>f.length>=o,room:()=>r-f.length,narrowTo:c=>{o=Math.min(o,c)},take(c){a.has(c.name)||f.length>=o||(a.add(c.name),f.push(c))}}}function K(r,f,a){const o=r.flat(),c=I(o),l=o.map(d=>({declaration:d,n:c(d,f)})).sort((d,O)=>O.n-d.n);if(l.length===0||l[0].n===0)return!1;l.length>1&&l[0].n>=l[1].n*2&&a.narrowTo(a.chosen.length+x);for(const{declaration:d}of l){if(a.full())break;a.take(d)}return!0}function P(r,f){for(let a=0;!f.full();a++){let o=!1;for(const c of r){const l=c[a];if(l!==void 0&&(o=!0,f.take(l),f.full()))break}if(!o)break}}function W(r,f={}){const a=new Set;for(const n of r){if(a.has(n.name))throw new Error(`Two buses are called "${n.name}".`);a.add(n.name)}const o=N(r,f.namespace),c=G(r,o),l=U(r,o),w=new Map(r.map(n=>[n,L(n)])),d=I([...w.values()]),O=$(f.limit,C),m=n=>c.get(n),g=(n,t)=>o.owned(n,t),y=()=>r.flatMap(n=>n.bus.all().map(t=>({...t,name:o.offered(n,t.name)}))),k=()=>r.flatMap(n=>v(n,o,n.bus.declarations())),B=(n,t)=>Object.fromEntries(Object.entries(t).map(([e,u])=>[o.offered(n,e),u])),D=n=>r.map(t=>({member:t,n:d(w.get(t),n)})).sort((t,e)=>e.n-t.n),h={all:y,missing:n=>n.filter(t=>!c.has(t)),get:n=>{const t=m(n),e=t?.bus.get(g(t,n));return e&&t?{...e,name:n}:e},effectOf:n=>{const t=m(n);return t?.bus.effectOf(g(t,n))},trustOf:n=>{const t=m(n);return t?.bus.trustOf(g(t,n))},destructive:()=>r.flatMap(n=>n.bus.destructive().map(t=>o.offered(n,t))),unannotated:()=>r.flatMap(n=>n.bus.unannotated().map(t=>o.offered(n,t))),declarations:n=>n?r.flatMap(t=>{const e=n.filter(s=>c.get(s)===t);if(e.length===0)return[];const u=e.map(s=>g(t,s));return v(t,o,t.bus.declarations(u))}):k(),anthropic:n=>T(n??k()),openai:n=>E(n??k()),markdown:(n,t)=>b(n??k(),t),select:(n="",t)=>{const e=_($(t,O));for(const p of l)e.take(p);const u=e.room();if(u<=0)return e.chosen;const s=D(A(n)),i=s.map(({member:p})=>v(p,o,p.bus.select(n,u)).filter(F=>!e.holds(F.name)));if(s.length>1&&s[0].n>0&&s[0].n>=s[1].n*2&&i[0].length>0){e.narrowTo(e.chosen.length+x);for(const p of i[0])e.take(p);return e.chosen}return K(i,A(n),e)||P(i,e),e.chosen},invoke:async(n,t)=>{const e=m(n);return e?{...await e.bus.invoke(g(e,n),t),name:n}:{name:n,args:t,status:"failed",output:"",error:`No tool called "${n}". Offered: ${[...c.keys()].join(", ")}`,latencyMs:0}},usage:()=>Object.assign({},...r.map(n=>B(n,n.bus.usage()))),step:(n,t)=>S(n,t),trace:(n="",t)=>j(h.select(n,t),(e,u)=>h.invoke(e,u)),only:n=>{const t=n.filter(s=>c.has(s)),e=()=>h.declarations(t),u={...h,all:()=>y().filter(s=>t.includes(s.name)),missing:s=>s.filter(i=>!t.includes(i)),declarations:s=>s?e().filter(i=>s.includes(i.name)):e(),anthropic:s=>T(s??e()),openai:s=>E(s??e()),markdown:(s,i)=>b(s??e(),i),select:(s="",i)=>h.select(s,i).filter(M=>t.includes(M.name)),trace:(s="",i)=>j(u.select(s,i),(M,p)=>h.invoke(M,p))};return u}};return h}export{W as busgroup};
|
package/dist/bus/index.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export { type ToolBus, toolbus } from "./bus.js";
|
|
2
|
-
export { type BusMember, busgroup } from "./group.js";
|
|
2
|
+
export { type BusGroupOptions, type BusMember, busgroup } from "./group.js";
|
|
3
|
+
export type { Namespacing } from "./namespace.js";
|
|
4
|
+
export type { Offering } from "./only.js";
|
|
3
5
|
export type { ToolBusOptions } from "./options.js";
|
|
6
|
+
export { priced, spentOn } from "./spent.js";
|
|
4
7
|
export { newTrace, type ToolTrace } from "./trace.js";
|
package/dist/bus/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{toolbus as e}from"./bus.js";import{busgroup as t}from"./group.js";import{
|
|
1
|
+
import{toolbus as e}from"./bus.js";import{busgroup as t}from"./group.js";import{priced as m,spentOn as x}from"./spent.js";import{newTrace as s}from"./trace.js";export{t as busgroup,s as newTrace,m as priced,x as spentOn,e as toolbus};
|
package/dist/bus/invoke.d.ts
CHANGED
|
@@ -6,4 +6,4 @@ import type { Tool, ToolResult } from "../types/index.js";
|
|
|
6
6
|
* step worth recording, not an exception that ends the run — the same argument
|
|
7
7
|
* `status` and `error` exist on a rollout for.
|
|
8
8
|
*/
|
|
9
|
-
export declare function invokeTool(byName: Map<string, Tool<never>>, calls: Map<string, number>, now: () => number, name: string, args: Record<string, unknown
|
|
9
|
+
export declare function invokeTool(byName: Map<string, Tool<never>>, calls: Map<string, number>, now: () => number, name: string, args: Record<string, unknown>, maxOutputChars?: number): Promise<ToolResult>;
|
package/dist/bus/invoke.js
CHANGED
|
@@ -1 +1,3 @@
|
|
|
1
|
-
import{FieldValidationError as d,validateFields as
|
|
1
|
+
import{FieldValidationError as d,validateFields as y}from"@spendgraph/sdk";import{readyArgs as g}from"./args.js";import{reasonFor as h,render as p}from"./output.js";function M(r,e){if(e===void 0||r.length<=e)return{output:r};const t=Math.max(0,e);return{output:`${r.slice(0,t)}
|
|
2
|
+
|
|
3
|
+
[cut: showing the first ${t} of ${r.length} characters]`,truncated:{kept:t,of:r.length}}}function $(r){const e=r?.results;if(!Array.isArray(e))return{};const t=e.filter(s=>!!s&&typeof s.url=="string"&&s.url!=="");return t.length?{sources:t}:{}}function k(r,e){if(!r.costOf)return{};try{const t=r.costOf(e);return typeof t=="number"&&Number.isFinite(t)&&t>=0?{costMicros:t}:{}}catch{return{}}}async function A(r,e,t,s,u,f){const i=t(),n=r.get(s),c={name:s,args:u,latencyMs:0,...n?.trust?{trust:n.trust}:{}};if(!n)return{...c,status:"failed",output:"",error:`No tool called "${s}". Offered: ${[...r.keys()].join(", ")}`};const a=y(u,n.args);if(a.length>0)return{...c,status:"failed",output:"",error:new d(a).message,latencyMs:t()-i};e.set(s,(e.get(s)??0)+1);const l=g(n.args,u);try{const o=await n.run(l);return{...c,status:"completed",...M(p(o),f),latencyMs:t()-i,...k(n,o),...$(o)}}catch(o){return{...c,status:"failed",output:"",error:h(o),latencyMs:t()-i}}}export{A as invokeTool};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { BusMember } from "./group.js";
|
|
2
|
+
/** How a grouped tool is named to the model. */
|
|
3
|
+
export type Namespacing = boolean | ((member: string, tool: string) => string);
|
|
4
|
+
export interface Naming {
|
|
5
|
+
/** What the model is offered, given a tool's own name on its bus. */
|
|
6
|
+
offered(member: BusMember, tool: string): string;
|
|
7
|
+
/** Back to the name the owning bus knows, given a name the model used. */
|
|
8
|
+
owned(member: BusMember, offered: string): string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Names every tool after the bus that runs it, or leaves them all alone.
|
|
12
|
+
*
|
|
13
|
+
* Off by default, because a name that says nothing about who runs it is a name
|
|
14
|
+
* worth fixing at the source. It is not always yours to fix: a group standing
|
|
15
|
+
* in front of several servers you do not own will meet two of them offering
|
|
16
|
+
* `search`, and refusing to start is not better than disambiguating.
|
|
17
|
+
*
|
|
18
|
+
* Built eagerly from what each bus holds, so the reverse lookup is complete
|
|
19
|
+
* before the first call rather than filled in as names happen to be asked for.
|
|
20
|
+
*/
|
|
21
|
+
export declare function naming(members: BusMember[], how: Namespacing | undefined): Naming;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const d="__",p=(o,t)=>`${o}__${t}`;function u(o,t){if(!t)return{offered:(n,e)=>e,owned:(n,e)=>e};const r=typeof t=="function"?t:p,m=new Map,s=new Map;for(const n of o){const e=new Map,f=new Map;for(const a of n.bus.all()){const c=r(n.name,a.name);e.set(a.name,c),f.set(c,a.name)}m.set(n.name,e),s.set(n.name,f)}return{offered:(n,e)=>m.get(n.name)?.get(e)??e,owned:(n,e)=>s.get(n.name)?.get(e)??e}}export{u as naming};
|