@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 @@
|
|
|
1
|
+
import{tool as f}from"../../tool/index.js";import{contextIds as l,fixedRoot as h,rootOf as p}from"../context.js";import{readSubgraph as x}from"../explore/subgraph.js";import{rendered as y}from"./render.js";const g="toon",u=10,w=50,I=3;async function T(e,n){if(!e.supersede||!n)return;const o=n.indexOf(":"),s=x(await e.client.getContextGraph({contextType:n.slice(0,o),contextName:n.slice(o+1),depth:I,...e.graphId?{graphId:e.graphId}:{}})),a=new Map;for(const r of s.edges)r.link_type==="contradicts"&&a.set(r.to,r.from);return r=>r.context_ids.map(t=>a.get(t)).find(t=>t!==void 0)}function k(e){const n=e.rootContextId!==void 0,o=h(e.rootContextId)??"your root";return f({name:"recall",effect:"readonly",description:"Searches your own memory for what was recorded about something and returns the matching notes, one per row with the contexts each is filed under. Cheap, so try it before a web search and before deep_recall. Use deep_recall instead when the answer is spread across several notes or needs a why. Ask one focused thing.",args:[{name:"question",type:"string",required:!0,maxLength:1e3,description:"What you want to know, in a sentence."},{name:"context_ids",type:"list",required:!1,description:n?`Contexts under ${o} to favour, as type:name. Take them from an earlier result or from explore_memory.`:"Only search these contexts, as type:name. Take them from an earlier result or from explore_memory."},{name:"limit",type:"number",required:!1,min:1,max:w,description:`Most notes to return. Default ${e.limit??u}.`}],run:async({question:s,context_ids:a,limit:r})=>{const t=p(e.rootContextId),i=l(a,e.defaultContextType),d=t?[t]:i,c=t&&i?i:void 0,m=(await e.client.retrieveMemories({query:s,limit:r??e.limit??u,format:"json",...e.graphId?{graphId:e.graphId}:{},...d?{contextIds:d}:{},...c?{boostContextIds:c}:{},...e.sources?.length?{sources:e.sources}:{}})).items??[];return m.length===0?"Nothing recorded about that.":y(m,e.format??g,await T(e,t))}})}export{k as recall};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { RecalledItem } from "../client.js";
|
|
2
|
+
export type RecallFormat = "toon" | "markdown";
|
|
3
|
+
/** Which context superseded this note's, when one did. */
|
|
4
|
+
export type Superseded = (item: RecalledItem) => string | undefined;
|
|
5
|
+
export declare const rendered: (items: RecalledItem[], format: RecallFormat, by?: Superseded) => string;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
const t=/[,:"\n\\]|^\s|\s$|^$/,d=n=>t.test(n)?`"${n.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\n/g,"\\n")}"`:n,c=n=>String(Math.round(n*100)),$=(n,o,e)=>[n.context_ids.join(";"),n.text,n.source??"",c(n.score),...e?[o??""]:[]],a=(n,o)=>{const e=o?"contexts,text,source,relevance,superseded_by":"contexts,text,source,relevance";return[`memories[${n.length}]{${e}}:`,...n.map(s=>` ${$(s,o?.(s),o!==void 0).map(d).join(",")}`)].join(`
|
|
2
|
+
`)},p=(n,o)=>n.map(e=>{const s=o?.(e),r=s?` \u2014 superseded by ${s}`:"";return`- [${e.context_ids.join(", ")}] ${e.text} (${e.source??"unknown"}, ${c(e.score)}%)${r}`}).join(`
|
|
3
|
+
`),u=(n,o,e)=>o==="toon"?a(n,e):p(n,e);export{u as rendered};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { MemoryGraph, MemorySource } from "../client.js";
|
|
2
|
+
export interface RememberOptions extends MemoryGraph {
|
|
3
|
+
source?: MemorySource;
|
|
4
|
+
defaultContextType?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function remember(opts: RememberOptions): import("../../index.js").Tool<import("../../index.js").ArgsOf<readonly [{
|
|
7
|
+
readonly name: "context";
|
|
8
|
+
readonly type: "string";
|
|
9
|
+
readonly required: true;
|
|
10
|
+
readonly maxLength: 200;
|
|
11
|
+
readonly description: "Where it is filed, as type:name.";
|
|
12
|
+
}, {
|
|
13
|
+
readonly name: "text";
|
|
14
|
+
readonly type: "string";
|
|
15
|
+
readonly required: true;
|
|
16
|
+
readonly maxLength: 4000;
|
|
17
|
+
readonly description: "The thing to remember, as a complete sentence or two.";
|
|
18
|
+
}, {
|
|
19
|
+
readonly name: "kind";
|
|
20
|
+
readonly type: "enum";
|
|
21
|
+
readonly required: false;
|
|
22
|
+
readonly options: readonly ["fact", "decision", "constraint", "feedback", "observation"];
|
|
23
|
+
readonly description: "What sort of thing it is. Default fact.";
|
|
24
|
+
}, {
|
|
25
|
+
readonly name: "details";
|
|
26
|
+
readonly type: "json";
|
|
27
|
+
readonly required: false;
|
|
28
|
+
readonly description: string;
|
|
29
|
+
}, {
|
|
30
|
+
readonly name: "extends";
|
|
31
|
+
readonly type: "list";
|
|
32
|
+
readonly required: false;
|
|
33
|
+
readonly description: "Contexts this is a more specific detail of, as type:name.";
|
|
34
|
+
}, {
|
|
35
|
+
readonly name: "related_to";
|
|
36
|
+
readonly type: "list";
|
|
37
|
+
readonly required: false;
|
|
38
|
+
readonly description: "Contexts this sits alongside, as type:name.";
|
|
39
|
+
}, {
|
|
40
|
+
readonly name: "reinforces";
|
|
41
|
+
readonly type: "list";
|
|
42
|
+
readonly required: false;
|
|
43
|
+
readonly description: "Contexts this backs up with more evidence, as type:name.";
|
|
44
|
+
}, {
|
|
45
|
+
readonly name: "contradicts";
|
|
46
|
+
readonly type: "list";
|
|
47
|
+
readonly required: false;
|
|
48
|
+
readonly description: "Contexts this supersedes, as type:name.";
|
|
49
|
+
}]>>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{tool as h}from"../../tool/index.js";import{contextId as y,contextIds as a,fixedRoot as x,rootOf as w}from"../context.js";const g=["fact","decision","constraint","feedback","observation"],b="assistant",C=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),q=(e,t)=>{if(t==null)return{data:e};if(!C(t))throw new Error("details must be a JSON object of named fields.");return{...t,data:e}},d=(e,t)=>t?.length?{[e]:t}:{};function v(e){const t=e.defaultContextType,u=e.rootContextId===void 0?"":` under ${x(e.rootContextId)??"your root"}`,m=(c,l)=>{const s=w(e.rootContextId),i=a(l,t)??[],r=s?[s,...i]:i;return[...new Set(r)].filter(n=>n!==c)};return h({name:"remember",effect:"idempotent",description:`Records one thing in your own memory${u} so a later run can recall it. File it under a context named type:name, like person:alice or decision:pricing_tier, reusing a name from explore_memory when one fits. One fact per call; the text should stand on its own without the conversation around it.`,args:[{name:"context",type:"string",required:!0,maxLength:200,description:"Where it is filed, as type:name."},{name:"text",type:"string",required:!0,maxLength:4e3,description:"The thing to remember, as a complete sentence or two."},{name:"kind",type:"enum",required:!1,options:g,description:"What sort of thing it is. Default fact."},{name:"details",type:"json",required:!1,description:"Structured fields worth keeping with the note, as a JSON object: amounts, dates, ids, URLs. The text is still what a recall shows; these ride along with it."},{name:"extends",type:"list",required:!1,description:"Contexts this is a more specific detail of, as type:name."},{name:"related_to",type:"list",required:!1,description:"Contexts this sits alongside, as type:name."},{name:"reinforces",type:"list",required:!1,description:"Contexts this backs up with more evidence, as type:name."},{name:"contradicts",type:"list",required:!1,description:"Contexts this supersedes, as type:name."}],run:async({context:c,text:l,kind:s="fact",details:i,...r})=>{const n=y(c,t),f={event_kind:s,context_id:n,source:e.source??b,payload:q(l,i),...d("extends",m(n,r.extends)),...d("related_to",a(r.related_to,t)),...d("reinforces",a(r.reinforces,t)),...d("contradicts",a(r.contradicts,t))},p=await e.client.storeEventsBatch([f],e.graphId),[o]=p.results;if(!o||o.status==="error")throw new Error(o?.error??"The memory was not stored, and the server did not say why.");return o.status==="filtered"?{stored:!1,context:n,reason:o.error??"judged not worth keeping"}:{stored:!0,context:n,relevance:o.relevance}}})}export{v as remember};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { Recency, SearchAsk, SearchDepth, Searched, Searcher, SearcherOptions, SearchUsage, Source, } from "./search.js";
|
|
2
|
+
export { DEPTHS, NO_TOKENS } from "./search.js";
|
|
3
|
+
export { type SearxngOptions, searxng, textOf } from "./searxng/index.js";
|
|
4
|
+
export { type TavilyOptions, tavily } from "./tavily/index.js";
|
|
5
|
+
export { readReply, type SonarOptions, sonar } from "./sonar/index.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{DEPTHS as e,NO_TOKENS as t}from"./search.js";import{searxng as f,textOf as p}from"./searxng/index.js";import{tavily as m}from"./tavily/index.js";import{readReply as l,sonar as n}from"./sonar/index.js";export{e as DEPTHS,t as NO_TOKENS,l as readReply,f as searxng,n as sonar,m as tavily,p as textOf};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { SearcherOptions } from "./search.js";
|
|
2
|
+
/**
|
|
3
|
+
* Sends again while the answer is a 429 or a 5xx and tries remain, then hands
|
|
4
|
+
* back whatever came last, ok or not, for the caller to read or refuse.
|
|
5
|
+
*/
|
|
6
|
+
export declare function retrying(opts: Pick<SearcherOptions, "attempts" | "sleep" | "random">): (send: () => Promise<Response>) => Promise<Response>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const M=500,f=8e3;function m(t){const r=t.headers.get("retry-after");if(!r)return null;const e=Number(r);return Number.isFinite(e)&&e>=0?e*1e3:null}const u=t=>t===429||t>=500;function A(t){const r=Math.max(1,t.attempts??3),e=t.sleep??(o=>new Promise(n=>setTimeout(n,o))),c=t.random??Math.random;return async o=>{for(let n=1;;n++){const s=await o();if(s.ok||!u(s.status)||n===r)return s;const a=Math.min(8e3,500*2**(n-1)),i=m(s)??a;await e(i+a*c())}}}export{A as retrying};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** How much searching one call is worth. */
|
|
2
|
+
export type SearchDepth = "quick" | "pro" | "deep";
|
|
3
|
+
export declare const DEPTHS: readonly SearchDepth[];
|
|
4
|
+
export type Recency = "day" | "week" | "month" | "year";
|
|
5
|
+
export interface Source {
|
|
6
|
+
url: string;
|
|
7
|
+
title?: string;
|
|
8
|
+
date?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface SearchUsage {
|
|
11
|
+
inputTokens: number;
|
|
12
|
+
outputTokens: number;
|
|
13
|
+
citationTokens: number;
|
|
14
|
+
reasoningTokens: number;
|
|
15
|
+
searchQueries: number;
|
|
16
|
+
}
|
|
17
|
+
/** What every searcher hands back, whoever did the searching. */
|
|
18
|
+
export interface Searched {
|
|
19
|
+
answer: string;
|
|
20
|
+
results: Source[];
|
|
21
|
+
model: string;
|
|
22
|
+
truncated: boolean;
|
|
23
|
+
usage: SearchUsage;
|
|
24
|
+
}
|
|
25
|
+
export interface SearchAsk {
|
|
26
|
+
query: string;
|
|
27
|
+
depth: SearchDepth;
|
|
28
|
+
recency?: Recency;
|
|
29
|
+
}
|
|
30
|
+
export type Searcher = ((ask: SearchAsk) => Promise<Searched>) & {
|
|
31
|
+
domains?: readonly string[];
|
|
32
|
+
};
|
|
33
|
+
/** What every searcher is configured with; each adds only where its answers come from. */
|
|
34
|
+
export interface SearcherOptions {
|
|
35
|
+
/** Hosts results are restricted to. Set here, so the model cannot widen it. */
|
|
36
|
+
domains?: string[];
|
|
37
|
+
/** Give up on a request after this. */
|
|
38
|
+
timeoutMs?: number;
|
|
39
|
+
/** Longest answer handed back. Default 20000 characters. */
|
|
40
|
+
maxChars?: number;
|
|
41
|
+
/** Longest source list handed back. Default 20. */
|
|
42
|
+
maxResults?: number;
|
|
43
|
+
/**
|
|
44
|
+
* Tries for a rate limit or a bad minute on the other side. Default 3.
|
|
45
|
+
*
|
|
46
|
+
* A wave of sub-questions searches in parallel, so two calls landing together
|
|
47
|
+
* is ordinary and a 429 on the second is not a failure worth surfacing — it
|
|
48
|
+
* is a wait. `retry-after` is honoured where the server sends one, and the
|
|
49
|
+
* wait is spread by up to one backoff on top: a whole wave limited at the
|
|
50
|
+
* same instant computes the same delay, and retrying in step rebuilds the
|
|
51
|
+
* burst that caused it.
|
|
52
|
+
*/
|
|
53
|
+
attempts?: number;
|
|
54
|
+
/** Injected for tests. */
|
|
55
|
+
fetch?: typeof fetch;
|
|
56
|
+
/** Injected for tests. */
|
|
57
|
+
sleep?: (ms: number) => Promise<void>;
|
|
58
|
+
/** Injected for tests. Returns 0 to 1, and spreads a retried wave apart. */
|
|
59
|
+
random?: () => number;
|
|
60
|
+
}
|
|
61
|
+
export declare const DEFAULT_MAX_CHARS = 20000;
|
|
62
|
+
export declare const DEFAULT_MAX_RESULTS = 20;
|
|
63
|
+
export declare const NO_TOKENS: SearchUsage;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const o=["quick","pro","deep"],e=2e4,t=20,n={inputTokens:0,outputTokens:0,citationTokens:0,reasoningTokens:0,searchQueries:1};export{e as DEFAULT_MAX_CHARS,t as DEFAULT_MAX_RESULTS,o as DEPTHS,n as NO_TOKENS};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{searxng as o}from"./searxng.js";import{textOf as x}from"./text.js";export{o as searxng,x as textOf};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type SearchDepth, type Searcher, type SearcherOptions } from "../search.js";
|
|
2
|
+
export interface SearxngOptions extends SearcherOptions {
|
|
3
|
+
/** The SearXNG instance, such as http://localhost:8080. Required. */
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
/** Pin which engines answer. Default: whatever the instance runs. */
|
|
6
|
+
engines?: string[];
|
|
7
|
+
/** Pin the language. Default: the instance's own. */
|
|
8
|
+
language?: string;
|
|
9
|
+
/** Text kept from each page read. Default 6000 characters. */
|
|
10
|
+
pageChars?: number;
|
|
11
|
+
/** Pages read at each depth. Default 5, 8 and 10. */
|
|
12
|
+
pages?: Partial<Record<SearchDepth, number>>;
|
|
13
|
+
}
|
|
14
|
+
export declare function searxng(opts: SearxngOptions): Searcher;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import{checkUrl as S}from"../../builtin/http/allow.js";import{retrying as y}from"../retry.js";import{DEFAULT_MAX_CHARS as N,DEFAULT_MAX_RESULTS as F,NO_TOKENS as x}from"../search.js";import{textOf as A}from"./text.js";const T="searxng",U=["*"],G=2e4,X=6e3,L=4e5,H={quick:5,pro:8,deep:10},I=/https?:\/\/[^\s"'<>)\]]+/,Q=2;function W(t){return!t.url||!t.title?null:{url:t.url,title:t.title,...t.publishedDate?{date:t.publishedDate}:{},snippet:t.content??""}}function q({url:t,title:r,date:i}){return{url:t,title:r,...i?{date:i}:{}}}function K(t,r){return`## ${t.title}
|
|
2
|
+
${t.url}
|
|
3
|
+
${r}`}const Y=3,v=t=>t.status>=300&&t.status<400;function R(t,r){if(!r?.length)return!0;const i=new URL(t.url).hostname.toLowerCase();return r.some(u=>{const h=u.toLowerCase().replace(/^\./,"");return i===h||i.endsWith(`.${h}`)})}function z(t){const r=new Set;return t.flatMap(i=>i.hits).filter(i=>!r.has(i.url)&&r.add(i.url))}function tt(t){if(!t.baseUrl?.trim())throw new Error("searxng needs the baseUrl of a SearXNG instance, such as http://localhost:8080.");const r=t.fetch??globalThis.fetch.bind(globalThis),i=t.baseUrl.replace(/\/+$/,""),u=t.timeoutMs??G,h=Math.max(1,t.maxResults??F),g=t.maxChars??N,f=t.pageChars??X,b={...H,...t.pages},O=y(t);async function d(n,s){const e=new URL(`${i}/search`);e.searchParams.set("q",n),e.searchParams.set("format","json"),s&&e.searchParams.set("time_range",s),t.language&&e.searchParams.set("language",t.language),t.engines?.length&&e.searchParams.set("engines",t.engines.join(","));const a=await O(()=>r(e,{signal:AbortSignal.timeout(u),headers:{accept:"application/json"}}));if(!a.ok){const o=a.status===403?" SearXNG refuses the json format unless settings.yml lists it under search.formats and the limiter is off.":"";throw new Error(`SearXNG returned ${a.status}.${o}`)}const c=await a.json();return{hits:(c.results??[]).flatMap(o=>{const l=W(o);return l&&R(l,t.domains)?[l]:[]}),suggestions:c.suggestions??[]}}async function P(n,s){const e=n.suggestions.slice(0,Q);return[n,...await Promise.all(e.map(a=>d(a,s)))]}async function p(n){const s=AbortSignal.timeout(u);let e=S(n,U),a=await r(e,{signal:s,redirect:"manual"});for(let c=0;c<Y&&v(a);c++){const o=a.headers.get("location");if(!o)break;e=S(new URL(o,a.url||e).toString(),U),a=await r(e,{signal:s,redirect:"manual"})}return a}async function M(n){try{const s=await p(n.url);if(!s.ok)return n.snippet;const e=A((await s.text()).slice(0,L));return e?e.slice(0,f):n.snippet}catch{return n.snippet}}async function D(n){if(!R({url:n,snippet:""},t.domains))return null;try{const s=await p(n);return s.ok&&A((await s.text()).slice(0,L)).slice(0,f)||null}catch{return null}}const C=(n,s)=>({answer:s.slice(0,g),results:[{url:n}],model:T,truncated:s.length>g,usage:{...x,searchQueries:0}});return Object.assign(async({query:n,depth:s,recency:e})=>{const a=n.match(I)?.[0],c=a?await D(a):null;if(a&&c)return C(a,c);const o=await d(n,e),l=s==="deep"?await P(o,e):[o],w=z(l),m=w.slice(0,h),_=m.slice(0,b[s]),k=await Promise.all(_.map(M)),E=_.map(($,j)=>K($,k[j])).join(`
|
|
4
|
+
|
|
5
|
+
`);return{answer:E.slice(0,g),results:m.map(q),model:T,truncated:E.length>g||w.length>m.length,usage:{...x,searchQueries:l.length}}},t.domains?.length?{domains:[...t.domains]}:{})}export{tt as searxng};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
const r=/<(nav|header|footer|aside|form)\b[\s\S]*?<\/\1>/gi,c=/<(main|article)\b[^>]*>([\s\S]*?)<\/\1>/i;function t(e){return e.match(c)?.[2]??e}function a(e){return t(e).replace(/<!--[\s\S]*?-->/g," ").replace(/<script[\s\S]*?<\/script>/gi," ").replace(/<style[\s\S]*?<\/style>/gi," ").replace(/<noscript[\s\S]*?<\/noscript>/gi," ").replace(r," ").replace(/<\/(p|div|li|h[1-6]|tr|br|section|article)>/gi,`
|
|
2
|
+
`).replace(/<[^>]+>/g," ").replace(/ /g," ").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'|'/g,"'").replace(/[ \t]+/g," ").replace(/\s*\n\s*/g,`
|
|
3
|
+
`).trim()}export{a as textOf};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{readReply as e}from"./reply.js";import{sonar as a}from"./sonar.js";export{e as readReply,a as sonar};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Searcher, type SearcherOptions } from "../search.js";
|
|
2
|
+
export interface SonarOptions extends SearcherOptions {
|
|
3
|
+
/** Perplexity key. Required, and read from here rather than the environment. */
|
|
4
|
+
apiKey: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The live web through Perplexity's Sonar models, one model per depth.
|
|
8
|
+
*
|
|
9
|
+
* Perplexity searches, reads and writes the answer itself, with citation
|
|
10
|
+
* marks, and bills citation and reasoning tokens on top of the usual two.
|
|
11
|
+
* The timeout defaults to a minute, or five on a deep search.
|
|
12
|
+
*/
|
|
13
|
+
export declare function sonar(opts: SonarOptions): Searcher;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{retrying as m}from"../retry.js";import{DEFAULT_MAX_CHARS as d,DEFAULT_MAX_RESULTS as h}from"../search.js";import{readReply as _}from"./reply.js";const E={quick:"sonar",pro:"sonar-pro",deep:"sonar-deep-research"},f="https://api.perplexity.ai/chat/completions",p=6e4,T=3e5,u=500;function A(e){if(!e.apiKey?.trim())throw new Error("sonar needs a Perplexity apiKey; it will not read one from the process.");const i=e.fetch??globalThis.fetch.bind(globalThis),a=m(e);return Object.assign(async({query:s,depth:o,recency:n})=>{const r=E[o],c=e.timeoutMs??(o==="deep"?T:p),t=await a(()=>i(f,{method:"POST",signal:AbortSignal.timeout(c),headers:{authorization:`Bearer ${e.apiKey}`,"content-type":"application/json"},body:JSON.stringify({model:r,messages:[{role:"user",content:s}],...n?{search_recency_filter:n}:{},...e.domains?.length?{search_domain_filter:e.domains}:{}})}));if(!t.ok){const l=(await t.text()).slice(0,u);throw new Error(`Perplexity returned ${t.status} for ${r}: ${l}`)}return _(await t.json(),r,e.maxChars??d,e.maxResults??h)},e.domains?.length?{domains:[...e.domains]}:{})}export{A as sonar};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{readReply as o}from"./reply.js";import{tavily as t}from"./tavily.js";export{o as readReply,t as tavily};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Searched } from "../search.js";
|
|
2
|
+
interface Hit {
|
|
3
|
+
url?: string;
|
|
4
|
+
title?: string;
|
|
5
|
+
content?: string;
|
|
6
|
+
raw_content?: string | null;
|
|
7
|
+
published_date?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface TavilyReply {
|
|
10
|
+
answer?: string | null;
|
|
11
|
+
results?: Hit[];
|
|
12
|
+
auto_parameters?: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Tavily's reply as a `Searched`.
|
|
16
|
+
*
|
|
17
|
+
* Its own `answer` leads where it gave one, because it is a summary written
|
|
18
|
+
* across every result; the per-result text follows so a later stage can quote
|
|
19
|
+
* a source rather than the summary of it.
|
|
20
|
+
*/
|
|
21
|
+
export declare function readReply(raw: TavilyReply, model: string, maxChars: number, maxResults: number): Searched;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import{NO_TOKENS as c}from"../search.js";function i(e){return e.url?{url:e.url,...e.title?{title:e.title}:{},...e.published_date?{date:e.published_date}:{}}:null}function a(e){const t=(e.raw_content??e.content??"").trim();return`## ${e.title??e.url}
|
|
2
|
+
${e.url}
|
|
3
|
+
${t}`}function p(e,t,r,s){const u=(e.results??[]).filter(l=>!!l.url),n=u.slice(0,s),o=[e.answer?.trim(),...n.map(a)].filter(Boolean).join(`
|
|
4
|
+
|
|
5
|
+
`);return{answer:o.slice(0,r),results:n.map(i).filter(l=>l!==null),model:t,truncated:o.length>r||u.length>n.length,usage:{...c}}}export{p as readReply};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Searcher, type SearcherOptions } from "../search.js";
|
|
2
|
+
export interface TavilyOptions extends SearcherOptions {
|
|
3
|
+
/** Tavily key. Required, and read from here rather than the environment. */
|
|
4
|
+
apiKey: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The live web through Tavily, which searches and extracts in one call.
|
|
8
|
+
*
|
|
9
|
+
* Built for retrieval rather than for a person reading a results page: it
|
|
10
|
+
* returns the text of each page alongside its url, so a finding keeps both
|
|
11
|
+
* what was said and where it was said. Depth buys a wider search and more
|
|
12
|
+
* pages read, not a different model.
|
|
13
|
+
*/
|
|
14
|
+
export declare function tavily(opts: TavilyOptions): Searcher;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{retrying as l}from"../retry.js";import{DEFAULT_MAX_CHARS as m,DEFAULT_MAX_RESULTS as n}from"../search.js";import{readReply as _}from"./reply.js";const u="https://api.tavily.com/search",y="tavily-search",g=6e4,p=500,T={quick:{search_depth:"basic",pages:3},pro:{search_depth:"advanced",pages:5},deep:{search_depth:"advanced",pages:10}},E={day:1,week:7,month:30,year:365};function S(e){if(!e.apiKey?.trim())throw new Error("tavily needs an apiKey; it will not read one from the process.");const i=e.fetch??globalThis.fetch.bind(globalThis),r=l(e);return Object.assign(async({query:s,depth:o,recency:t})=>{const{search_depth:c,pages:d}=T[o],a=await r(()=>i(u,{method:"POST",signal:AbortSignal.timeout(e.timeoutMs??g),headers:{authorization:`Bearer ${e.apiKey}`,"content-type":"application/json"},body:JSON.stringify({query:s,search_depth:c,include_answer:!0,include_raw_content:!0,max_results:Math.min(d,e.maxResults??n),...t?{days:E[t]}:{},...e.domains?.length?{include_domains:e.domains}:{}})}));if(!a.ok){const h=(await a.text()).slice(0,p);throw new Error(`Tavily returned ${a.status}: ${h}`)}return _(await a.json(),y,e.maxChars??m,e.maxResults??n)},e.domains?.length?{domains:[...e.domains]}:{})}export{S as tavily};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{replaying as e}from"./replay.js";import{memoryStore as p}from"./store.js";export{p as memoryStore,e as replaying};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ToolResult } from "../types/index.js";
|
|
2
|
+
/** A call the recording had no answer for. */
|
|
3
|
+
export interface Unrecorded {
|
|
4
|
+
name: string;
|
|
5
|
+
args: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
export interface ReplayOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Tools that must still run for real, by name.
|
|
10
|
+
*
|
|
11
|
+
* A replay stands in for the world, not for the agent's own bookkeeping.
|
|
12
|
+
* `finish` carries the answer — the thing actually under test — so replaying
|
|
13
|
+
* it would mean a reworded prompt could only ever produce the old wording,
|
|
14
|
+
* and the run stalls the first time it says anything differently. `think` and
|
|
15
|
+
* `notes` are the same: they change nothing outside the process, so there is
|
|
16
|
+
* nothing to stand in for.
|
|
17
|
+
*
|
|
18
|
+
* Their recordings are dropped rather than kept unused, because a result
|
|
19
|
+
* nothing was ever going to ask for is not a step the new run skipped.
|
|
20
|
+
*/
|
|
21
|
+
live?: readonly string[];
|
|
22
|
+
/** What runs the tools named in `live`. Required with it, and refused without. */
|
|
23
|
+
on?: {
|
|
24
|
+
invoke(name: string, args: Record<string, unknown>): Promise<ToolResult>;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export interface Replay {
|
|
28
|
+
/** Hands back what the recording holds, or a failed result saying it does not. */
|
|
29
|
+
invoke(name: string, args: Record<string, unknown>): Promise<ToolResult>;
|
|
30
|
+
/** Calls this run made that the recording could not answer, in order. */
|
|
31
|
+
unrecorded: Unrecorded[];
|
|
32
|
+
/** Recorded results this run never asked for. */
|
|
33
|
+
unused(): ToolResult[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The tool results a run already produced, handed back instead of running anything.
|
|
37
|
+
*
|
|
38
|
+
* What a recorded trace is for: re-run the reasoning against a new prompt or a
|
|
39
|
+
* new model while the tools answer exactly as they did, so the only thing that
|
|
40
|
+
* changed is the thing under test. Nothing reaches a database, an API or a
|
|
41
|
+
* customer, and the comparison is not confounded by the world having moved.
|
|
42
|
+
*
|
|
43
|
+
* Matched on the arguments as well as the name, which is the whole value and
|
|
44
|
+
* also the part that looks like a limitation. A replay that answered
|
|
45
|
+
* `track("ORD-9")` with the recording of `track("ORD-1")` would be a green run
|
|
46
|
+
* proving nothing. A call the recording cannot answer is the finding: the new
|
|
47
|
+
* prompt asked something the old one never did, and the recording is not
|
|
48
|
+
* evidence about it either way.
|
|
49
|
+
*
|
|
50
|
+
* It offers no `effectOf`, and that is not an omission. Nothing runs during a
|
|
51
|
+
* replay, so nothing is destructive — a rail that gates on effect has nothing
|
|
52
|
+
* to guard, and giving it the original effects would have it blocking calls
|
|
53
|
+
* that are already only paper.
|
|
54
|
+
*/
|
|
55
|
+
export declare function replaying(recorded: readonly ToolResult[], opts?: ReplayOptions): Replay;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const u=(i,n,e)=>i.name===n&&JSON.stringify(i.args)===JSON.stringify(e),f=(i,n,e)=>({name:i,args:n,status:"failed",output:"",error:`Nothing recorded for ${i}(${JSON.stringify(n)}). The recording holds: ${[...new Set(e.map(o=>o.name))].join(", ")||"nothing"}`,latencyMs:0});function l(i,n={}){const e=new Set(n.live??[]);if(e.size>0&&!n.on)throw new Error(`replaying names ${[...e].join(", ")} as live, but nothing to run them on.`);const o=i.filter(t=>!e.has(t.name)),s=new Set,c=[];return{unrecorded:c,unused:()=>o.filter((t,r)=>!s.has(r)),invoke:async(t,r)=>{if(e.has(t))return n.on.invoke(t,r);const a=o.findIndex((d,h)=>!s.has(h)&&u(d,t,r));return a===-1?(c.push({name:t,args:r}),f(t,r,o)):(s.add(a),{...o[a]})}}}export{l as replaying};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { MemoryClient, MemorySource } from "../memories/client.js";
|
|
2
|
+
export interface StoredMemory {
|
|
3
|
+
id: string;
|
|
4
|
+
context: string;
|
|
5
|
+
kind: string;
|
|
6
|
+
source: string;
|
|
7
|
+
text: string;
|
|
8
|
+
details: Record<string, unknown>;
|
|
9
|
+
extends: string[];
|
|
10
|
+
related_to: string[];
|
|
11
|
+
reinforces: string[];
|
|
12
|
+
contradicts: string[];
|
|
13
|
+
}
|
|
14
|
+
export interface ObservedMoment {
|
|
15
|
+
source: string;
|
|
16
|
+
payload: string | Record<string, unknown>;
|
|
17
|
+
stream?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface MemorySeed {
|
|
20
|
+
context: string;
|
|
21
|
+
text: string;
|
|
22
|
+
details?: Record<string, unknown>;
|
|
23
|
+
kind?: string;
|
|
24
|
+
source?: MemorySource;
|
|
25
|
+
extends?: string[];
|
|
26
|
+
related_to?: string[];
|
|
27
|
+
reinforces?: string[];
|
|
28
|
+
contradicts?: string[];
|
|
29
|
+
}
|
|
30
|
+
export interface MemoryStore extends MemoryClient {
|
|
31
|
+
memories: StoredMemory[];
|
|
32
|
+
observed: ObservedMoment[];
|
|
33
|
+
remember(seed: MemorySeed): StoredMemory;
|
|
34
|
+
}
|
|
35
|
+
export declare function memoryStore(seeds?: MemorySeed[]): MemoryStore;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
const O={policy:.95,verified:.9,tool:.8,document:.75,user:.7,assistant:.6,derived:.55,system:.5},g=c=>new Set(c.toLowerCase().split(/[^a-z0-9]+/).filter(o=>o.length>2));function w(c,o){const r=g(c),i=g(`${o.text} ${o.context.replace(":"," ")}`);let d=0;for(const p of r)i.has(p)&&(d+=1);return r.size===0?0:d/r.size}const z=c=>O[c]??.5,$=(c,o)=>c.map(r=>({memory:r,score:w(o,r)*.75+z(r.source)*.25})).filter(r=>w(o,r.memory)>0).sort((r,i)=>i.score-r.score),k=c=>[...c.extends,...c.related_to,...c.reinforces,...c.contradicts],h=(c,o,r,i=new Set)=>o===r?!0:i.has(o)?!1:(i.add(o),c.filter(d=>d.context===o).flatMap(k).some(d=>h(c,d,r,i))),v=(c,o,r)=>!r?.length||r.some(i=>h(c,o.context,i)||k(o).some(d=>h(c,d,i)));function T(c=[]){const o=[],r=[];let i=1;const d=t=>{const s={id:`m${i++}`,context:t.context,kind:t.kind??"fact",source:t.source??"assistant",text:t.text,details:t.details??{},extends:t.extends??[],related_to:t.related_to??[],reinforces:t.reinforces??[],contradicts:t.contradicts??[]};return o.push(s),s},p=()=>{const t=new Map;for(const s of o)t.set(s.context,(t.get(s.context)??0)+1);return[...t].map(([s,e])=>{const n=s.indexOf(":");return{context_id:s,context_type:s.slice(0,n),context_name:s.slice(n+1),reference_count:e}})},S=t=>{const s=[];for(const e of o)e.context===t?(s.push(...e.extends.map(n=>`extends \u2192 ${n}`)),s.push(...e.related_to.map(n=>`related_to \u2192 ${n}`)),s.push(...e.reinforces.map(n=>`reinforces \u2192 ${n}`)),s.push(...e.contradicts.map(n=>`contradicts \u2192 ${n}`))):(e.extends.includes(t)&&s.push(`extended_by \u2190 ${e.context}`),e.related_to.includes(t)&&s.push(`related_to \u2190 ${e.context}`),e.reinforces.includes(t)&&s.push(`reinforced_by \u2190 ${e.context}`),e.contradicts.includes(t)&&s.push(`contradicted_by \u2190 ${e.context}`));return[...new Set(s)]},b=t=>t.map((s,e)=>` ${e===t.length-1?"\u2514\u2500":"\u251C\u2500"} ${s}`),y=t=>`${t.map(s=>[s.context_id,...b(S(s.context_id))].join(`
|
|
2
|
+
`)).join(`
|
|
3
|
+
`)}
|
|
4
|
+
`;for(const t of c)d(t);return{memories:o,observed:r,remember:d,async retrieveMemories(t){const e=$(o.filter(n=>v(o,n,t.contextIds)),t.query).filter(({memory:n})=>!t.sources?.length||t.sources.includes(n.source)).slice(0,t.limit??10).map(({memory:n,score:l})=>({locus_id:n.id,context_ids:[n.context,...n.extends,...n.related_to,...n.reinforces].sort(),text:n.text,score:l,source:n.source,kind:n.kind}));return{items_found:e.length,items:e}},async deepRecall(t){const e=$(o.filter(n=>v(o,n,t.contextIds)),t.question).map(({memory:n,score:l})=>({text:n.text,source:n.source,locus:n.id,contexts:[n.context],score:l}));return{facts:e,contexts_walked:[...new Set(e.flatMap(n=>n.contexts))],...e[0]?{summary:e[0].text}:{},coverage:e.length?"full":"partial",fallback:!1,usage:{cost_usd:0}}},async storeEventsBatch(t){const s=t.map(e=>{const{data:n,...l}=e.payload;return d({context:e.context_id??"topic:untitled",text:typeof n=="string"?n:JSON.stringify(e.payload),details:l,kind:e.event_kind,source:e.source,extends:e.extends,related_to:e.related_to,reinforces:e.reinforces,contradicts:e.contradicts}),{status:"recorded",event_id:`m${i-1}`,relevance:"high"}});return{stored:s.length,filtered:0,failed:0,results:s}},async searchContexts(t,s,e){const n=t.toLowerCase(),l=p().filter(x=>!e?.context_type||x.context_type===e.context_type).filter(x=>x.context_id.toLowerCase().includes(n)).slice(0,e?.page_size??20);return{contexts:l,total:l.length,...e?.include==="relationships"?{relationships_text:y(l)}:{}}},async listContextTypes(){const t=new Map;for(const e of p())t.set(e.context_type,(t.get(e.context_type)??0)+e.reference_count);const s=[...t].map(([e,n])=>({context_type:e,count:n}));return{context_types:s,total:s.length}},async getContextGraph(t){const s=`${t.contextType}:${t.contextName}`,e=t.depth??1,n=p(),l=o.flatMap(a=>[...a.extends.map(f=>({from:a.context,to:f,link_type:"extends"})),...a.related_to.map(f=>({from:a.context,to:f,link_type:"related_to"})),...a.reinforces.map(f=>({from:a.context,to:f,link_type:"reinforces"})),...a.contradicts.map(f=>({from:a.context,to:f,link_type:"contradicts"}))]),x=new Set([s]);let _=[s];for(let a=0;a<e&&_.length;a+=1){const f=l.filter(u=>_.includes(u.from)||_.includes(u.to)).flatMap(u=>[u.from,u.to]).filter(u=>!x.has(u));for(const u of f)x.add(u);_=[...new Set(f)]}const M=n.filter(a=>x.has(a.context_id)),C=[...new Set(l.filter(a=>x.has(a.from)&&x.has(a.to)))];return{nodes:M,edges:C}},async observe(t){return r.push({source:t.source,payload:t.payload,...t.stream?{stream:t.stream}:{}}),{observed:!0}}}}export{T as memoryStore};
|
package/dist/tool/tool.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ArgSpec, ArgsOf, Effect, Tool } from "../types/index.js";
|
|
1
|
+
import type { ArgSpec, ArgsOf, Effect, Tool, Trust } from "../types/index.js";
|
|
2
2
|
export interface ToolSpec<T extends readonly ArgSpec[]> {
|
|
3
3
|
name: string;
|
|
4
4
|
description: string;
|
|
@@ -6,6 +6,9 @@ export interface ToolSpec<T extends readonly ArgSpec[]> {
|
|
|
6
6
|
aliases?: readonly string[];
|
|
7
7
|
pinned?: boolean;
|
|
8
8
|
effect?: Effect;
|
|
9
|
+
trust?: Trust;
|
|
10
|
+
costOf?(value: unknown): number | undefined;
|
|
11
|
+
needs?: readonly string[];
|
|
9
12
|
run(args: ArgsOf<T>): Promise<unknown> | unknown;
|
|
10
13
|
}
|
|
11
14
|
/**
|
package/dist/tool/tool.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const r=/^[A-Za-z_][A-Za-z0-9_]*$/,i=["readonly","idempotent","destructive"];function
|
|
1
|
+
const r=/^[A-Za-z_][A-Za-z0-9_]*$/,i=["readonly","idempotent","destructive"],a=["own","external"];function s(n,t){if(!t.options?.length)throw new Error(`Tool "${n}" argument "${t.name}" is an enum with no options, so nothing can satisfy it.`);const e=new Set;for(const o of t.options){if(!o.trim())throw new Error(`Tool "${n}" argument "${t.name}" has a blank option, which the model can pick and you cannot act on.`);if(e.has(o))throw new Error(`Tool "${n}" argument "${t.name}" lists "${o}" twice.`);e.add(o)}}function f(n,t){if(t){for(const e of t)if(!e.trim())throw new Error(`Tool "${n}" has a blank alias, which matches every query or none.`)}}function h(n,t){const e=new Set;for(const o of t){if(!r.test(o.name))throw new Error(`Tool "${n}" argument "${o.name}" is not a usable name.`);if(e.has(o.name))throw new Error(`Tool "${n}" declares "${o.name}" twice.`);e.add(o.name),o.type==="enum"&&s(n,o)}}function d(n){if(!r.test(n.name))throw new Error(`Tool name "${n.name}" must be letters, digits and underscores, starting with a letter.`);if(!n.description.trim())throw new Error(`Tool "${n.name}" has no description. It is the only thing the model reads to decide whether to call it.`);if(h(n.name,n.args),f(n.name,n.aliases),n.effect!==void 0&&!i.includes(n.effect))throw new Error(`Tool "${n.name}" declares effect "${n.effect}", which is not one of ${i.join(", ")}.`);if(n.trust!==void 0&&!a.includes(n.trust))throw new Error(`Tool "${n.name}" declares trust "${n.trust}", which is not one of ${a.join(", ")}.`);return{name:n.name,description:n.description,args:n.args.map(t=>({...t,options:t.options?[...t.options]:void 0})),...n.aliases?.length?{aliases:[...n.aliases]}:{},pinned:n.pinned,...n.effect?{effect:n.effect}:{},...n.trust?{trust:n.trust}:{},...n.costOf?{costOf:n.costOf}:{},...n.needs?.length?{needs:[...n.needs]}:{},run:n.run}}export{d as tool};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export type { ToolResult } from "./result.js";
|
|
2
|
-
export type { ArgSpec, ArgsOf, ArgValue, Effect, Tool, ToolDeclaration } from "./tool.js";
|
|
2
|
+
export type { ArgSpec, ArgsOf, ArgValue, Effect, Tool, ToolDeclaration, Trust } from "./tool.js";
|
package/dist/types/result.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { Source } from "../search/search.js";
|
|
2
|
+
import type { Trust } from "./tool.js";
|
|
1
3
|
/** What happened when one was called. */
|
|
2
4
|
export interface ToolResult {
|
|
3
5
|
name: string;
|
|
@@ -7,4 +9,40 @@ export interface ToolResult {
|
|
|
7
9
|
output: string;
|
|
8
10
|
error?: string;
|
|
9
11
|
latencyMs: number;
|
|
12
|
+
/**
|
|
13
|
+
* What the tool declared about where its content came from.
|
|
14
|
+
*
|
|
15
|
+
* Carried onto the result so a hook watching what has already run can tell
|
|
16
|
+
* that untrusted content entered the context, without matching on a name.
|
|
17
|
+
*/
|
|
18
|
+
trust?: Trust;
|
|
19
|
+
/** What this call itself cost, when the tool declared how to read it. */
|
|
20
|
+
costMicros?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Where the content came from, when the tool returned it apart from the prose.
|
|
23
|
+
*
|
|
24
|
+
* A search tool already knows each result's url; rendering the output flattens
|
|
25
|
+
* that into text nobody downstream can read back. Carried here so a citation
|
|
26
|
+
* survives as a link rather than as something to parse out of a paragraph.
|
|
27
|
+
*/
|
|
28
|
+
sources?: Source[];
|
|
29
|
+
/**
|
|
30
|
+
* Set when the output was cut to fit, with what it was cut from.
|
|
31
|
+
*
|
|
32
|
+
* Present only when something was actually dropped, so its absence is the
|
|
33
|
+
* claim that the output is whole.
|
|
34
|
+
*/
|
|
35
|
+
truncated?: {
|
|
36
|
+
kept: number;
|
|
37
|
+
of: number;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Set when this exact destructive call had already run and the earlier result
|
|
41
|
+
* was handed back instead of running it again.
|
|
42
|
+
*
|
|
43
|
+
* The output is the first call's, unchanged, because it is still true — the
|
|
44
|
+
* refund did happen. This is for whoever counts how often the agent asked
|
|
45
|
+
* twice, which is a fact about the agent and not about the answer.
|
|
46
|
+
*/
|
|
47
|
+
repeated?: true;
|
|
10
48
|
}
|
package/dist/types/tool.d.ts
CHANGED
|
@@ -66,6 +66,22 @@ export type ArgsOf<T extends readonly ArgSpec[]> = {
|
|
|
66
66
|
* the same as safe.
|
|
67
67
|
*/
|
|
68
68
|
export type Effect = "readonly" | "idempotent" | "destructive";
|
|
69
|
+
/**
|
|
70
|
+
* Where what a tool returns came from.
|
|
71
|
+
*
|
|
72
|
+
* `own` is yours: a database you run, a file you wrote, a calculation. `external`
|
|
73
|
+
* is content from outside that somebody else can write — a fetched page, a
|
|
74
|
+
* search result, an inbox, a document a customer uploaded. What comes back from
|
|
75
|
+
* one of those is data, and anything in it that reads like an instruction was
|
|
76
|
+
* put there by whoever controls the source.
|
|
77
|
+
*
|
|
78
|
+
* Declared rather than inferred, and separate from `effect`, because the two are
|
|
79
|
+
* independent: reading a web page is `readonly` and `external`, reading your own
|
|
80
|
+
* ledger is `readonly` and `own`, and only the first can carry an instruction
|
|
81
|
+
* written by a stranger. Unset means nobody has said, which is not the same as
|
|
82
|
+
* safe.
|
|
83
|
+
*/
|
|
84
|
+
export type Trust = "own" | "external";
|
|
69
85
|
/**
|
|
70
86
|
* A description the model reads and a function you wrote, in one object.
|
|
71
87
|
*
|
|
@@ -97,6 +113,51 @@ export interface Tool<Args = Record<string, unknown>> {
|
|
|
97
113
|
pinned?: boolean;
|
|
98
114
|
/** What calling it does outside the process. Unset means unknown. */
|
|
99
115
|
effect?: Effect;
|
|
116
|
+
/**
|
|
117
|
+
* Whether what it returns is content somebody outside wrote. Unset means unknown.
|
|
118
|
+
*
|
|
119
|
+
* A guard that wants to say "no destructive call after untrusted content came
|
|
120
|
+
* in" has to gate on something the tool declared. Without this the only thing
|
|
121
|
+
* left to gate on is the tool's name, which the hook documentation warns
|
|
122
|
+
* against for the reason it always does: a rename turns the rule off, silently
|
|
123
|
+
* and in the permissive direction.
|
|
124
|
+
*/
|
|
125
|
+
trust?: Trust;
|
|
126
|
+
/**
|
|
127
|
+
* Reads what one call cost, in micro-USD, out of what `run` returned.
|
|
128
|
+
*
|
|
129
|
+
* Some tools spend money: a deep recall runs model calls of its own, a judge
|
|
130
|
+
* tool pays for a verdict, a sub-agent tool pays for a whole run. That spend
|
|
131
|
+
* is invisible to the loop that called them — token counts belong to the
|
|
132
|
+
* outer model call and a tool's own bill is not in them. Without this the
|
|
133
|
+
* number exists only inside the stringified result, where the model reads it
|
|
134
|
+
* and no code can add it up.
|
|
135
|
+
*
|
|
136
|
+
* A reader rather than a field on the result, because `run` returns whatever
|
|
137
|
+
* suits the tool and only the tool knows which part of it is the price.
|
|
138
|
+
*/
|
|
139
|
+
costOf?(value: unknown): number | undefined;
|
|
140
|
+
/**
|
|
141
|
+
* Tools that have to be offered alongside this one, or not at all.
|
|
142
|
+
*
|
|
143
|
+
* Selection scores each tool on its own, which is the wrong unit when two of
|
|
144
|
+
* them are a cheap entry point and its expensive counterpart. `deep_recall`
|
|
145
|
+
* and `recall` overlap on almost every term, so a shortlist of three routinely
|
|
146
|
+
* kept the dear one and dropped the cheap one — while both descriptions tell
|
|
147
|
+
* the model to try the cheap one first. The model was then instructed to
|
|
148
|
+
* prefer a tool it had not been given.
|
|
149
|
+
*
|
|
150
|
+
* Enforced as a guarantee rather than a preference: where the shortlist has
|
|
151
|
+
* no room for the pair, the tool that declared the need is dropped instead.
|
|
152
|
+
* Offering the expensive half alone is the failure this exists to stop, and a
|
|
153
|
+
* best-effort version would still do it whenever the ceiling was tight.
|
|
154
|
+
*
|
|
155
|
+
* A name no tool on the bus carries is ignored rather than refused. The
|
|
156
|
+
* constraint is about what a shortlist offers, and a partner that is not on
|
|
157
|
+
* the bus cannot be dropped in favour of anything — a bus holding this tool
|
|
158
|
+
* alone has no selection problem to have.
|
|
159
|
+
*/
|
|
160
|
+
needs?: readonly string[];
|
|
100
161
|
run(args: Args): Promise<unknown> | unknown;
|
|
101
162
|
}
|
|
102
163
|
/** A tool as the model sees it: no implementation, no pin. */
|