@spendgraph/tools 0.6.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/README.md +3 -2
- package/dist/agents/index.d.ts +1 -0
- package/dist/agents/index.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/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 +10 -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 +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/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 +21 -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/only.js +0 -0
- package/dist/bus/options.d.ts +31 -0
- 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/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/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 +4 -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 +61 -0
- 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 +22 -9
- package/dist/builtin/deep-recall/deep-recall.js +0 -1
- package/dist/builtin/write-document/commission.js +0 -13
- package/dist/builtin/write-document/index.d.ts +0 -6
- package/dist/builtin/write-document/index.js +0 -1
- package/dist/builtin/write-document/write-document.js +0 -1
- /package/dist/{builtin/write-document → agents/moa}/design.d.ts +0 -0
- /package/dist/{builtin/write-document → agents/moa}/design.js +0 -0
- /package/dist/{builtin/write-document → agents/moa}/http.d.ts +0 -0
- /package/dist/{builtin/write-document → agents/moa}/http.js +0 -0
- /package/dist/{builtin/write-document → agents/moa}/types.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,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,12 +1,16 @@
|
|
|
1
1
|
export { calculate, evaluate } from "./calculate/index.js";
|
|
2
2
|
export { type ClockOptions, currentTime } from "./clock/index.js";
|
|
3
3
|
export { type CodeExecOptions, codeExec, type Sandbox, type SandboxRequest, type SandboxResult, } from "./code-exec/index.js";
|
|
4
|
-
export { dateMath } from "./date/index.js";
|
|
5
|
-
export { type
|
|
6
|
-
export { type EscalateOptions, type EscalationRequest, escalate } from "./escalate/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";
|
|
7
8
|
export { answerOf, type Finished, finish, isFinished } from "./finish/index.js";
|
|
8
|
-
export { checkUrl, type HttpOptions, httpRequest } from "./http/index.js";
|
|
9
|
-
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";
|
|
10
14
|
export { think } from "./think/index.js";
|
|
11
15
|
export { type SearchDepth, type WebSearchOptions, webSearch } from "./web-search/index.js";
|
|
12
|
-
export { type
|
|
16
|
+
export { type WikidataApi, type WikidataOptions, wikidata } from "./wikidata/index.js";
|
package/dist/builtin/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{calculate as o,evaluate as t}from"./calculate/index.js";import{currentTime as
|
|
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 +1 @@
|
|
|
1
|
-
import{jsonQuery as e}from"./json.js";import{readPath as
|
|
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};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{tool as
|
|
1
|
+
import{tool as s}from"../../tool/index.js";import{jsonFrom as i}from"./parse.js";import{readPath as l}from"./path.js";const d=2e4;function g(r={}){const e=r.maxChars??d;return s({name:"json_query",effect:"readonly",description:"Reads one value out of a JSON document by path. Paths look like `invoices[0].total`, or `invoices[*].total` for every element. Use it instead of reading a long document yourself.",args:[{name:"json",type:"text",required:!0,maxLength:2e5},{name:"path",type:"string",required:!0,maxLength:500}],run:({json:a,path:n})=>{const u=i(a),t=l(u,n);if(t===void 0)return{found:!1,path:n,value:null};const o=JSON.stringify(t)??"null";return{found:!0,path:n,value:t,truncated:o.length>e,...o.length>e?{value:`${o.slice(0,e)}\u2026`}:{}}}})}export{g as jsonQuery};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The JSON inside a model's reply, whatever it wrapped it in.
|
|
3
|
+
*
|
|
4
|
+
* The two commonest structured-output failures are not bad JSON at all: a
|
|
5
|
+
* fenced block, and a sentence before or after it. Both parse perfectly once
|
|
6
|
+
* the payload is found, and every caller otherwise writes the same fence
|
|
7
|
+
* stripping by hand and gets the second case wrong.
|
|
8
|
+
*
|
|
9
|
+
* It finds the JSON; it never rewrites it. A trailing comma, a single-quoted
|
|
10
|
+
* key or a missing brace stays an error, because repairing those is guessing at
|
|
11
|
+
* what was meant and a wrong guess is a confidently wrong record. Those belong
|
|
12
|
+
* in a bounded repair round, where the model is told what failed and answers
|
|
13
|
+
* again.
|
|
14
|
+
*/
|
|
15
|
+
export declare function jsonFrom(text: string): unknown;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const l=/```(?:[a-zA-Z0-9_-]+)?\s*\n?([\s\S]*?)```/,f={"{":"}","[":"]"};function a(e){const n=e.search(/[[{]/);if(n===-1)return null;const t=f[e[n]];let o=0,i=!1,s=!1;for(let r=n;r<e.length;r+=1){const c=e[r];if(s){s=!1;continue}if(c==="\\"&&i){s=!0;continue}if(c==='"'){i=!i;continue}if(!i){if(c===e[n])o+=1;else if(c===t&&(o-=1,o===0))return e.slice(n,r+1)}}return null}function u(e){const n=e.trim();for(const t of[n,l.exec(n)?.[1]?.trim(),a(n)])if(t)try{return JSON.parse(t)}catch{}throw new Error(`No JSON in ${n.length>80?`${n.slice(0,80)}\u2026`:n||"an empty reply"}`)}export{u as jsonFrom};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ACTIONS, type NotesOptions, type NotesStore, notes, notesStore } from "./notes.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{ACTIONS as t,notes as r,notesStore as n}from"./notes.js";export{t as ACTIONS,r as notes,n as notesStore};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** Boards by session, one per process unless you hand every bus the same one. */
|
|
2
|
+
export type NotesStore = Map<string, string[]>;
|
|
3
|
+
export interface NotesOptions {
|
|
4
|
+
/** Whose board: a session id, so a follow-up question sees what the last run worked out. */
|
|
5
|
+
session: string;
|
|
6
|
+
/** Where boards live. Default one store for the process; pass your own to share or to inspect. */
|
|
7
|
+
store?: NotesStore;
|
|
8
|
+
/** Notes a board holds before `post` is refused. Default 50. */
|
|
9
|
+
maxNotes?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare const ACTIONS: readonly ["post", "read", "replace", "clear"];
|
|
12
|
+
export declare function notesStore(): NotesStore;
|
|
13
|
+
/**
|
|
14
|
+
* A board the model writes to and reads back during a session — what it is
|
|
15
|
+
* doing, what is still missing, what it decided — kept in memory and gone when
|
|
16
|
+
* the process is.
|
|
17
|
+
*
|
|
18
|
+
* `think` is write-once and never seen again; this is the model's own chosen
|
|
19
|
+
* state, readable three steps later. It is not memory: a note worth keeping
|
|
20
|
+
* goes to `remember`, on purpose, so half-thoughts never surface as facts.
|
|
21
|
+
*
|
|
22
|
+
* `replace` is what makes it usable as a running plan. Append-only, a plan
|
|
23
|
+
* re-posted each turn becomes twenty notes where the twentieth contradicts the
|
|
24
|
+
* third and `read` shows both — and the only way back is `clear`, which throws
|
|
25
|
+
* the plan away with the noise. One note, rewritten, is the whole pattern.
|
|
26
|
+
*
|
|
27
|
+
* No effect label: `read` changes nothing and `post` changes only this board.
|
|
28
|
+
*/
|
|
29
|
+
export declare function notes(opts: NotesOptions): import("../../index.js").Tool<import("../../index.js").ArgsOf<readonly [{
|
|
30
|
+
readonly name: "action";
|
|
31
|
+
readonly type: "enum";
|
|
32
|
+
readonly required: true;
|
|
33
|
+
readonly options: readonly ["post", "read", "replace", "clear"];
|
|
34
|
+
}, {
|
|
35
|
+
readonly name: "note";
|
|
36
|
+
readonly type: "text";
|
|
37
|
+
readonly required: false;
|
|
38
|
+
readonly maxLength: 2000;
|
|
39
|
+
}, {
|
|
40
|
+
readonly name: "number";
|
|
41
|
+
readonly type: "number";
|
|
42
|
+
readonly required: false;
|
|
43
|
+
readonly min: 1;
|
|
44
|
+
readonly description: "Which note to replace, as `read` numbers them.";
|
|
45
|
+
}]>>;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{tool as d}from"../../tool/index.js";const l=["post","read","replace","clear"],u=50,c=2e3,w=new Map;function p(){return new Map}function m(e){return e.map((t,n)=>`#${n+1} ${t}`).join(`
|
|
2
|
+
`)}function f(e,t,n){if(t===void 0)throw new Error("replace needs the `number` of the note.");if(e.length===0)throw new Error("There is nothing on the board to replace.");if(t<1||t>e.length)throw new Error(`There is no note #${t}; the board holds #1 to #${e.length}.`);const o=[...e];return o[t-1]=n,o}function y(e){if(!e.session?.trim())throw new Error("notes needs the session whose board this is.");const t=e.store??w,n=e.maxNotes??u,o=()=>t.get(e.session)??[];return d({name:"notes",description:"Your notes for this session. `post` writes one \u2014 what you are doing, what is still missing, what you decided. `read` shows them all, numbered, in order. `replace` rewrites one by its number \u2014 keep a plan as a single note and rewrite it as you go, rather than posting its status again each turn. `clear` wipes them. They last the session and nobody else reads them; use `remember` for anything that should outlast it.",args:[{name:"action",type:"enum",required:!0,options:[...l]},{name:"note",type:"text",required:!1,maxLength:c},{name:"number",type:"number",required:!1,min:1,description:"Which note to replace, as `read` numbers them."}],run:({action:s,note:h,number:a})=>{const r=o();if(s==="read")return r.length?m(r):"Nothing on the board yet.";if(s==="clear")return t.delete(e.session),`Cleared ${r.length} note${r.length===1?"":"s"}.`;const i=h?.trim();if(!i)throw new Error(`${s} needs a \`note\`.`);if(s==="replace")return t.set(e.session,f(r,a,i)),`#${a} rewritten.`;if(r.length>=n)throw new Error(`The board holds ${n} notes; clear it, or say less.`);return t.set(e.session,[...r,i]),`#${r.length+1} noted.`}})}export{l as ACTIONS,y as notes,p as notesStore};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type PoliteOptions } from "../polite.js";
|
|
2
|
+
export interface Paper {
|
|
3
|
+
title: string;
|
|
4
|
+
year: number | null;
|
|
5
|
+
authors: string[];
|
|
6
|
+
venue: string | null;
|
|
7
|
+
citations: number;
|
|
8
|
+
doi: string | null;
|
|
9
|
+
pdf: string | null;
|
|
10
|
+
abstract: string;
|
|
11
|
+
}
|
|
12
|
+
export interface OpenAlexApi {
|
|
13
|
+
search(query: string, limit: number): Promise<Paper[]>;
|
|
14
|
+
}
|
|
15
|
+
export interface ApiOptions extends Omit<PoliteOptions, "userAgent"> {
|
|
16
|
+
/** Your address, sent as `mailto`; OpenAlex answers named callers from a faster pool. */
|
|
17
|
+
mailto?: string;
|
|
18
|
+
}
|
|
19
|
+
/** OpenAlex stores an abstract as word → positions; this puts the words back in order. */
|
|
20
|
+
export declare function abstractOf(index: Record<string, number[]> | null | undefined): string;
|
|
21
|
+
/** The one OpenAlex call the tool makes, behind an interface a test can fake. */
|
|
22
|
+
export declare function openAlexApi(opts?: ApiOptions): OpenAlexApi;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{politeGet as n}from"../polite.js";const i="https://api.openalex.org/works",c="spendgraph-tools (https://spendgraph.locusgraph.com)",l=["id","doi","title","publication_year","cited_by_count","authorships","primary_location","open_access","abstract_inverted_index"].join(",");function p(t){if(!t)return"";const e=[];for(const[o,s]of Object.entries(t))for(const r of s)e[r]=o;return e.filter(o=>o!==void 0).join(" ")}function u(t){return{title:t.title?.trim()||"(untitled)",year:t.publication_year??null,authors:(t.authorships??[]).flatMap(e=>e.author?.display_name?[e.author.display_name]:[]),venue:t.primary_location?.source?.display_name??null,citations:t.cited_by_count??0,doi:t.doi?.replace(/^https?:\/\/doi\.org\//,"")??null,pdf:t.open_access?.oa_url??t.primary_location?.pdf_url??null,abstract:p(t.abstract_inverted_index)}}function h(t={}){const e=n({...t,userAgent:c});return{async search(o,s){const r=new URL(i);r.searchParams.set("search",o),r.searchParams.set("per-page",String(s)),r.searchParams.set("select",l),t.mailto&&r.searchParams.set("mailto",t.mailto);const a=await e(r);if(!a.ok)throw new Error(`OpenAlex answered ${a.status}.`);return((await a.json()).results??[]).map(u)}}}export{p as abstractOf,h as openAlexApi};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{abstractOf as o,openAlexApi as p}from"./api.js";import{openalex as x}from"./openalex.js";import{rendered as a}from"./render.js";export{o as abstractOf,p as openAlexApi,x as openalex,a as rendered};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ApiOptions, type OpenAlexApi } from "./api.js";
|
|
2
|
+
export interface OpenAlexOptions extends ApiOptions {
|
|
3
|
+
/** Papers returned. Default 5. */
|
|
4
|
+
maxResults?: number;
|
|
5
|
+
/** Abstract kept per paper. Default 400 characters. */
|
|
6
|
+
maxAbstractChars?: number;
|
|
7
|
+
/** Injected for tests, in place of the call to OpenAlex. */
|
|
8
|
+
api?: OpenAlexApi;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Papers on a topic from OpenAlex, as text — title, authors, venue, how often
|
|
12
|
+
* cited, a DOI, a PDF when one is open, and the start of the abstract.
|
|
13
|
+
*
|
|
14
|
+
* Free and unauthenticated. For what the literature says; not for anything
|
|
15
|
+
* recent, commercial, or outside a published paper.
|
|
16
|
+
*/
|
|
17
|
+
export declare function openalex(opts?: OpenAlexOptions): import("../../index.js").Tool<import("../../index.js").ArgsOf<readonly [{
|
|
18
|
+
readonly name: "question";
|
|
19
|
+
readonly type: "string";
|
|
20
|
+
readonly required: true;
|
|
21
|
+
readonly maxLength: 300;
|
|
22
|
+
readonly description: "The topic, as a phrase a paper title or abstract would contain.";
|
|
23
|
+
}]>>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{tool as i}from"../../tool/index.js";import{openAlexApi as c}from"./api.js";import{rendered as h}from"./render.js";const p=5,m=400;function l(t={}){const a=t.api??c(t),n=t.maxResults??p,o=t.maxAbstractChars??m;return i({name:"paper_search",effect:"readonly",trust:"external",description:"Finds published papers on a topic and returns them as text: title, authors, venue, citation count, DOI, an open PDF when there is one, and the start of the abstract. Use it for what research has found or measured, and cite the DOI. Not for prices, products, news or anything from the last few months.",args:[{name:"question",type:"string",required:!0,maxLength:300,description:"The topic, as a phrase a paper title or abstract would contain."}],run:async({question:s})=>{const e=s.trim();if(!e)throw new Error("Name the topic to search for.");const r=await a.search(e,n);if(r.length===0)throw new Error(`OpenAlex has no papers matching "${e}".`);return h(e,r,o)}})}export{l as openalex};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
const h=3;function c(n){const t=n.authors.slice(0,3).map(e=>e.split(" ").at(-1)??e),o=n.authors.length-t.length;return t.length===0?"unknown authors":o>0?`${t.join(", ")} +${o}`:t.join(", ")}function $(n){const t=n.year===null?"":` (${n.year})`,o=n.venue?` \xB7 ${n.venue}`:"";return`${n.title}${t} \u2014 ${c(n)}${o} \xB7 ${n.citations} citations`}function d(n){const o=[n.doi?`doi:${n.doi}`:null,n.pdf?`pdf: ${n.pdf}`:null].filter(e=>e!==null);return o.length>0?o.join(" \xB7 "):null}function f(n,t){const o=n.abstract.trim();return o?o.length>t?`${o.slice(0,t).trimEnd()}\u2026`:o:null}function m(n,t,o){const e=`${t.length} paper${t.length===1?"":"s"} on "${n}", most relevant first`,u=t.map((l,s)=>[`${s+1}. ${$(l)}`,d(l),f(l,o)].filter(i=>i!==null).map((i,r)=>r===0?i:` ${i}`).join(`
|
|
2
|
+
`));return[e,...u].join(`
|
|
3
|
+
|
|
4
|
+
`)}export{m as rendered};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface PoliteOptions {
|
|
2
|
+
/** Named on every request, as the open APIs ask, so a shared IP is still identifiable. */
|
|
3
|
+
userAgent: string;
|
|
4
|
+
timeoutMs?: number;
|
|
5
|
+
/** Injected for tests. */
|
|
6
|
+
fetch?: typeof fetch;
|
|
7
|
+
/** Injected for tests, in place of sleeping out a Retry-After. */
|
|
8
|
+
wait?: (ms: number) => Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A GET that behaves the way the free, keyless APIs ask: named, timed out,
|
|
12
|
+
* and waiting out a 429 for as long as Retry-After says before asking again.
|
|
13
|
+
*/
|
|
14
|
+
export declare function politeGet(opts: PoliteOptions): (url: URL) => Promise<Response>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const a=429,_=2,c=1e3,R=5e3,u=1e4,o=t=>new Promise(e=>setTimeout(e,t));function E(t){const e=Number(t.headers.get("retry-after")),n=Number.isFinite(e)&&e>0?e*1e3:1e3;return Math.min(n,5e3)}function A(t){const e=t.fetch??globalThis.fetch.bind(globalThis),n=t.wait??o,T=t.timeoutMs??1e4;return async i=>{for(let r=0;;r++){const s=await e(i,{signal:AbortSignal.timeout(T),headers:{"user-agent":t.userAgent,"api-user-agent":t.userAgent}});if(s.status!==429||r>=2)return s;await n(E(s))}}}export{A as politeGet};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { type Publisher, type PublishOptions, publish } from "./publish.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{publish as r}from"./publish.js";export{r as publish};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { PublishedFile, PublishInput, PublishTo } from "@spendgraph/sdk";
|
|
2
|
+
/** The part of the spendgraph client this needs: `new Spendgraph({ apiKey }).files` satisfies it. */
|
|
3
|
+
export interface Publisher {
|
|
4
|
+
publish(input: PublishInput): Promise<{
|
|
5
|
+
file: PublishedFile;
|
|
6
|
+
}>;
|
|
7
|
+
}
|
|
8
|
+
export interface PublishOptions {
|
|
9
|
+
/** Reaches the app with the key and the project pin; the tool never holds a credential. */
|
|
10
|
+
files: Publisher;
|
|
11
|
+
/** The project a file is filed under. A key publishes only into its own. */
|
|
12
|
+
projectId: string;
|
|
13
|
+
/** Formats this deployment will produce. Default all four. */
|
|
14
|
+
formats?: readonly PublishTo[];
|
|
15
|
+
/** Longest content accepted. Default 200000 characters. */
|
|
16
|
+
maxChars?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Content out as a file at a public URL — markdown, a page, plain text or a
|
|
20
|
+
* PDF — so a model can hand a person something to open, not a wall of text.
|
|
21
|
+
*
|
|
22
|
+
* The app renders and stores it; this only asks. A new file every call, so it
|
|
23
|
+
* carries no effect label: a repeat is a second file, never a lost one.
|
|
24
|
+
*/
|
|
25
|
+
export declare function publish(opts: PublishOptions): import("../../index.js").Tool<import("../../index.js").ArgsOf<readonly [{
|
|
26
|
+
readonly name: "content";
|
|
27
|
+
readonly type: "text";
|
|
28
|
+
readonly required: true;
|
|
29
|
+
readonly maxLength: number;
|
|
30
|
+
}, {
|
|
31
|
+
readonly name: "to";
|
|
32
|
+
readonly type: "enum";
|
|
33
|
+
readonly required: true;
|
|
34
|
+
readonly options: readonly ("md" | "pdf" | "html" | "txt")[];
|
|
35
|
+
}, {
|
|
36
|
+
readonly name: "from";
|
|
37
|
+
readonly type: "enum";
|
|
38
|
+
readonly required: false;
|
|
39
|
+
readonly options: readonly ["text", "md", "html"];
|
|
40
|
+
}, {
|
|
41
|
+
readonly name: "name";
|
|
42
|
+
readonly type: "string";
|
|
43
|
+
readonly required: false;
|
|
44
|
+
readonly maxLength: 120;
|
|
45
|
+
readonly description: "The file's name without its extension.";
|
|
46
|
+
}]>>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{PUBLISH_FROM as m,PUBLISH_TO as u}from"@spendgraph/sdk";import{tool as h}from"../../tool/index.js";const l=2e5,d="md";function p(e){if(!e.files)throw new Error("publish needs the client's files resource to reach the app.");if(!e.projectId?.trim())throw new Error("publish needs the projectId a file is filed under.");const r=e.formats?.length?e.formats:u,o=e.maxChars??l;return h({name:"publish",description:`Turns content into a file people can open and returns its public URL. Formats: ${r.join(", ")}. Say what the content is written in \u2014 markdown unless told otherwise \u2014 and what to make of it. Use it when the person asked for a document, a report or a download, not for a short answer.`,args:[{name:"content",type:"text",required:!0,maxLength:o},{name:"to",type:"enum",required:!0,options:[...r]},{name:"from",type:"enum",required:!1,options:[...m]},{name:"name",type:"string",required:!1,maxLength:120,description:"The file's name without its extension."}],run:async({content:i,to:s,from:a,name:n})=>{const{file:t}=await e.files.publish({projectId:e.projectId,content:i,from:a??d,to:s,...n?{name:n}:{}});return`Published ${t.name} (${t.bytes} bytes): ${t.url}`}})}export{p as publish};
|
|
@@ -1,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,21 @@
|
|
|
1
|
+
import { type ApiOptions, type WikidataApi } from "./api.js";
|
|
2
|
+
export interface WikidataOptions extends ApiOptions {
|
|
3
|
+
/** Facts kept per thing. Default 30; a country has hundreds. */
|
|
4
|
+
maxFacts?: number;
|
|
5
|
+
/** Injected for tests, in place of the three calls to Wikidata. */
|
|
6
|
+
api?: WikidataApi;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Facts about a thing from Wikidata, as text — its label, what it is, and
|
|
10
|
+
* the statements made about it, with every id resolved to a name.
|
|
11
|
+
*
|
|
12
|
+
* Free and unauthenticated. Entity, date, number and place claims are what it
|
|
13
|
+
* settles; prices, versions and anything recent are not in it.
|
|
14
|
+
*/
|
|
15
|
+
export declare function wikidata(opts?: WikidataOptions): import("../../index.js").Tool<import("../../index.js").ArgsOf<readonly [{
|
|
16
|
+
readonly name: "question";
|
|
17
|
+
readonly type: "string";
|
|
18
|
+
readonly required: true;
|
|
19
|
+
readonly maxLength: 200;
|
|
20
|
+
readonly description: "The thing's name, optionally followed by ': ' and what about it.";
|
|
21
|
+
}]>>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{tool as w}from"../../tool/index.js";import{wikidataApi as b}from"./api.js";import{factsOf as g,referenced as k,rendered as y,statementsOf as x}from"./render.js";const E=30;function L(t){const a=t.indexOf(":");if(a<1)return{name:t.trim(),about:null};const o=t.slice(a+1).trim();return{name:t.slice(0,a).trim(),about:o||null}}function A(t,a){const o=a.toLowerCase().split(/\s+/).filter(Boolean);return t.filter(i=>{const r=i.property.toLowerCase();return o.some(n=>r.includes(n))})}function $(t={}){const a=t.api??b(t),o=t.maxFacts??E;return w({name:"wikidata",effect:"readonly",trust:"external",description:"Looks a thing up on Wikidata and returns what is recorded about it as plain text: what it is, when it began, where it is, who runs it, and so on. Name the thing \u2014 a company, person, place, product or event \u2014 and add ': ' and a topic to narrow it, as in 'Cloudflare: founded' or 'Paris: population'. Not for prices, versions or news.",args:[{name:"question",type:"string",required:!0,maxLength:200,description:"The thing's name, optionally followed by ': ' and what about it."}],run:async({question:i})=>{const{name:r,about:n}=L(i);if(!r)throw new Error("Name the thing to look up.");const s=await a.search(r);if(!s)throw new Error(`Wikidata has nothing named "${r}".`);const e=await a.entity(s.id);if(!e)throw new Error(`Wikidata has no record for ${s.id}.`);const c=x(e).slice(0,o),f=c.flatMap(([p,m])=>[p,...m.flatMap(h=>k(h.mainsnak))]),u=await a.labels(f),d=g(c,u),l=n?A(d,n):d;if(n&&l.length===0)throw new Error(`Wikidata records nothing about "${n}" for ${e.label}.`);return y(e,l)}})}export{$ 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;
|