@spendgraph/tools 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +26 -258
  3. package/dist/agents/index.d.ts +1 -0
  4. package/dist/agents/index.js +1 -0
  5. package/dist/agents/moa/design.js +5 -0
  6. package/dist/agents/moa/http.js +1 -0
  7. package/dist/{builtin/write-document/write-document.d.ts → agents/moa/index.d.ts} +7 -2
  8. package/dist/agents/moa/index.js +1 -0
  9. package/dist/{builtin/write-document/commission.d.ts → agents/moa/moa.d.ts} +15 -15
  10. package/dist/agents/moa/moa.js +13 -0
  11. package/dist/{builtin/write-document → agents/moa}/types.d.ts +9 -9
  12. package/dist/agents/moa/types.js +0 -0
  13. package/dist/bogus/ask-mum.d.ts +5 -0
  14. package/dist/bogus/ask-mum.js +1 -0
  15. package/dist/bogus/book-taxi.d.ts +9 -0
  16. package/dist/bogus/book-taxi.js +1 -0
  17. package/dist/bogus/bus.d.ts +8 -0
  18. package/dist/bogus/bus.js +1 -0
  19. package/dist/bogus/calculator.d.ts +14 -0
  20. package/dist/bogus/calculator.js +1 -0
  21. package/dist/bogus/dictionary.d.ts +6 -0
  22. package/dist/bogus/dictionary.js +1 -0
  23. package/dist/bogus/feed-cat.d.ts +8 -0
  24. package/dist/bogus/feed-cat.js +1 -0
  25. package/dist/bogus/football-score.d.ts +6 -0
  26. package/dist/bogus/football-score.js +1 -0
  27. package/dist/bogus/gold-price.d.ts +7 -0
  28. package/dist/bogus/gold-price.js +1 -0
  29. package/dist/bogus/index.d.ts +14 -0
  30. package/dist/bogus/index.js +1 -0
  31. package/dist/bogus/my-calendar.d.ts +6 -0
  32. package/dist/bogus/my-calendar.js +1 -0
  33. package/dist/bogus/my-notes.d.ts +6 -0
  34. package/dist/bogus/my-notes.js +1 -0
  35. package/dist/bogus/my-recipes.d.ts +6 -0
  36. package/dist/bogus/my-recipes.js +1 -0
  37. package/dist/bogus/order-pizza.d.ts +12 -0
  38. package/dist/bogus/order-pizza.js +1 -0
  39. package/dist/bogus/prayer-times.d.ts +6 -0
  40. package/dist/bogus/prayer-times.js +1 -0
  41. package/dist/bogus/receipt.d.ts +8 -0
  42. package/dist/bogus/receipt.js +1 -0
  43. package/dist/builtin/calculate/calculate.d.ts +2 -0
  44. package/dist/builtin/calculate/calculate.js +1 -21
  45. package/dist/builtin/calculate/exact.d.ts +16 -0
  46. package/dist/builtin/calculate/exact.js +1 -0
  47. package/dist/builtin/calculate/index.d.ts +2 -1
  48. package/dist/builtin/calculate/index.js +1 -2
  49. package/dist/builtin/calculate/parse.js +1 -137
  50. package/dist/builtin/clock/clock.js +1 -61
  51. package/dist/builtin/clock/index.js +1 -1
  52. package/dist/builtin/code-exec/code-exec.d.ts +42 -0
  53. package/dist/builtin/code-exec/code-exec.js +1 -0
  54. package/dist/builtin/code-exec/index.d.ts +1 -0
  55. package/dist/builtin/code-exec/index.js +1 -0
  56. package/dist/builtin/date/date.d.ts +1 -1
  57. package/dist/builtin/date/date.js +1 -41
  58. package/dist/builtin/date/index.d.ts +1 -0
  59. package/dist/builtin/date/index.js +1 -2
  60. package/dist/builtin/date/shift.d.ts +1 -1
  61. package/dist/builtin/date/shift.js +1 -47
  62. package/dist/builtin/date/words.d.ts +13 -0
  63. package/dist/builtin/date/words.js +1 -0
  64. package/dist/builtin/decline/decline.d.ts +49 -0
  65. package/dist/builtin/decline/decline.js +1 -0
  66. package/dist/builtin/decline/index.d.ts +1 -0
  67. package/dist/builtin/decline/index.js +1 -0
  68. package/dist/builtin/escalate/escalate.d.ts +19 -0
  69. package/dist/builtin/escalate/escalate.js +1 -20
  70. package/dist/builtin/escalate/index.d.ts +1 -1
  71. package/dist/builtin/escalate/index.js +1 -1
  72. package/dist/builtin/extract/extract.d.ts +25 -0
  73. package/dist/builtin/extract/extract.js +1 -0
  74. package/dist/builtin/extract/index.d.ts +3 -0
  75. package/dist/builtin/extract/index.js +1 -0
  76. package/dist/builtin/extract/kinds.d.ts +6 -0
  77. package/dist/builtin/extract/kinds.js +1 -0
  78. package/dist/builtin/extract/readers.d.ts +7 -0
  79. package/dist/builtin/extract/readers.js +9 -0
  80. package/dist/builtin/finish/finish.d.ts +4 -0
  81. package/dist/builtin/finish/finish.js +1 -26
  82. package/dist/builtin/finish/index.js +1 -1
  83. package/dist/builtin/http/allow.d.ts +2 -0
  84. package/dist/builtin/http/allow.js +1 -37
  85. package/dist/builtin/http/hops.d.ts +10 -0
  86. package/dist/builtin/http/hops.js +1 -0
  87. package/dist/builtin/http/http.d.ts +9 -1
  88. package/dist/builtin/http/http.js +1 -41
  89. package/dist/builtin/http/index.d.ts +2 -0
  90. package/dist/builtin/http/index.js +1 -2
  91. package/dist/builtin/http/retry-after.d.ts +14 -0
  92. package/dist/builtin/http/retry-after.js +1 -0
  93. package/dist/builtin/index.d.ts +11 -6
  94. package/dist/builtin/index.js +1 -11
  95. package/dist/builtin/json/index.d.ts +1 -0
  96. package/dist/builtin/json/index.js +1 -2
  97. package/dist/builtin/json/json.js +1 -37
  98. package/dist/builtin/json/parse.d.ts +15 -0
  99. package/dist/builtin/json/parse.js +1 -0
  100. package/dist/builtin/json/path.js +1 -56
  101. package/dist/builtin/notes/index.d.ts +1 -0
  102. package/dist/builtin/notes/index.js +1 -0
  103. package/dist/builtin/notes/notes.d.ts +45 -0
  104. package/dist/builtin/notes/notes.js +2 -0
  105. package/dist/builtin/openalex/api.d.ts +22 -0
  106. package/dist/builtin/openalex/api.js +1 -0
  107. package/dist/builtin/openalex/index.d.ts +4 -0
  108. package/dist/builtin/openalex/index.js +1 -0
  109. package/dist/builtin/openalex/openalex.d.ts +23 -0
  110. package/dist/builtin/openalex/openalex.js +1 -0
  111. package/dist/builtin/openalex/render.d.ts +2 -0
  112. package/dist/builtin/openalex/render.js +4 -0
  113. package/dist/builtin/polite.d.ts +14 -0
  114. package/dist/builtin/polite.js +1 -0
  115. package/dist/builtin/publish/index.d.ts +1 -0
  116. package/dist/builtin/publish/index.js +1 -0
  117. package/dist/builtin/publish/publish.d.ts +46 -0
  118. package/dist/builtin/publish/publish.js +1 -0
  119. package/dist/builtin/think/index.js +1 -1
  120. package/dist/builtin/think/think.js +1 -12
  121. package/dist/builtin/web-search/index.js +1 -1
  122. package/dist/builtin/web-search/web-search.d.ts +10 -30
  123. package/dist/builtin/web-search/web-search.js +1 -103
  124. package/dist/builtin/wikidata/api.d.ts +40 -0
  125. package/dist/builtin/wikidata/api.js +1 -0
  126. package/dist/builtin/wikidata/index.d.ts +5 -0
  127. package/dist/builtin/wikidata/index.js +1 -0
  128. package/dist/builtin/wikidata/render.d.ts +13 -0
  129. package/dist/builtin/wikidata/render.js +2 -0
  130. package/dist/builtin/wikidata/wikidata.d.ts +21 -0
  131. package/dist/builtin/wikidata/wikidata.js +1 -0
  132. package/dist/bus/args.d.ts +11 -0
  133. package/dist/bus/args.js +1 -0
  134. package/dist/bus/bus.d.ts +23 -1
  135. package/dist/bus/bus.js +1 -49
  136. package/dist/bus/group.d.ts +21 -0
  137. package/dist/bus/group.js +1 -0
  138. package/dist/bus/index.d.ts +4 -0
  139. package/dist/bus/index.js +1 -2
  140. package/dist/bus/invoke.d.ts +1 -1
  141. package/dist/bus/invoke.js +3 -43
  142. package/dist/bus/namespace.d.ts +21 -0
  143. package/dist/bus/namespace.js +1 -0
  144. package/dist/bus/once.d.ts +32 -0
  145. package/dist/bus/once.js +1 -0
  146. package/dist/bus/only.d.ts +29 -0
  147. package/dist/bus/only.js +0 -0
  148. package/dist/bus/options.d.ts +31 -0
  149. package/dist/bus/options.js +0 -1
  150. package/dist/bus/output.d.ts +26 -0
  151. package/dist/bus/output.js +2 -0
  152. package/dist/bus/overlap.js +1 -31
  153. package/dist/bus/score.d.ts +11 -9
  154. package/dist/bus/score.js +1 -12
  155. package/dist/bus/select.d.ts +2 -17
  156. package/dist/bus/select.js +1 -44
  157. package/dist/bus/spent.d.ts +16 -0
  158. package/dist/bus/spent.js +1 -0
  159. package/dist/bus/step.js +1 -11
  160. package/dist/bus/terms.d.ts +25 -0
  161. package/dist/bus/terms.js +1 -0
  162. package/dist/bus/trace.d.ts +14 -1
  163. package/dist/bus/trace.js +1 -22
  164. package/dist/index.d.ts +16 -4
  165. package/dist/index.js +1 -3
  166. package/dist/internals.d.ts +9 -2
  167. package/dist/internals.js +1 -10
  168. package/dist/memories/client.d.ts +91 -0
  169. package/dist/memories/client.js +0 -0
  170. package/dist/memories/context.d.ts +5 -0
  171. package/dist/memories/context.js +1 -0
  172. package/dist/memories/deep-recall/deep-recall.d.ts +67 -0
  173. package/dist/memories/deep-recall/deep-recall.js +1 -0
  174. package/dist/memories/deep-recall/index.d.ts +1 -0
  175. package/dist/memories/deep-recall/index.js +1 -0
  176. package/dist/memories/explore/explore.d.ts +18 -0
  177. package/dist/memories/explore/explore.js +7 -0
  178. package/dist/memories/explore/subgraph.d.ts +12 -0
  179. package/dist/memories/explore/subgraph.js +3 -0
  180. package/dist/memories/index.d.ts +30 -0
  181. package/dist/memories/index.js +1 -0
  182. package/dist/memories/observe/observe.d.ts +7 -0
  183. package/dist/memories/observe/observe.js +1 -0
  184. package/dist/memories/recall/recall.d.ts +42 -0
  185. package/dist/memories/recall/recall.js +1 -0
  186. package/dist/memories/recall/render.d.ts +5 -0
  187. package/dist/memories/recall/render.js +3 -0
  188. package/dist/memories/remember/remember.d.ts +49 -0
  189. package/dist/memories/remember/remember.js +1 -0
  190. package/dist/search/index.d.ts +5 -0
  191. package/dist/search/index.js +1 -0
  192. package/dist/search/retry.d.ts +6 -0
  193. package/dist/search/retry.js +1 -0
  194. package/dist/search/search.d.ts +63 -0
  195. package/dist/search/search.js +1 -0
  196. package/dist/search/searxng/index.d.ts +2 -0
  197. package/dist/search/searxng/index.js +1 -0
  198. package/dist/search/searxng/searxng.d.ts +14 -0
  199. package/dist/search/searxng/searxng.js +5 -0
  200. package/dist/search/searxng/text.d.ts +2 -0
  201. package/dist/search/searxng/text.js +3 -0
  202. package/dist/search/sonar/index.d.ts +2 -0
  203. package/dist/search/sonar/index.js +1 -0
  204. package/dist/search/sonar/reply.js +1 -0
  205. package/dist/search/sonar/sonar.d.ts +13 -0
  206. package/dist/search/sonar/sonar.js +1 -0
  207. package/dist/search/tavily/index.d.ts +4 -0
  208. package/dist/search/tavily/index.js +1 -0
  209. package/dist/search/tavily/reply.d.ts +22 -0
  210. package/dist/search/tavily/reply.js +5 -0
  211. package/dist/search/tavily/tavily.d.ts +14 -0
  212. package/dist/search/tavily/tavily.js +1 -0
  213. package/dist/testing/index.d.ts +2 -0
  214. package/dist/testing/index.js +1 -0
  215. package/dist/testing/replay.d.ts +55 -0
  216. package/dist/testing/replay.js +1 -0
  217. package/dist/testing/store.d.ts +35 -0
  218. package/dist/testing/store.js +4 -0
  219. package/dist/tool/bind.js +1 -64
  220. package/dist/tool/index.js +1 -2
  221. package/dist/tool/tool.d.ts +5 -1
  222. package/dist/tool/tool.js +1 -51
  223. package/dist/types/index.d.ts +1 -1
  224. package/dist/types/index.js +0 -1
  225. package/dist/types/result.d.ts +38 -0
  226. package/dist/types/result.js +0 -1
  227. package/dist/types/tool.d.ts +74 -0
  228. package/dist/types/tool.js +0 -1
  229. package/dist/wire/index.js +1 -3
  230. package/dist/wire/json-schema.js +1 -65
  231. package/dist/wire/markdown.d.ts +5 -3
  232. package/dist/wire/markdown.js +9 -29
  233. package/dist/wire/providers.js +1 -18
  234. package/docs/bogus.mdx +85 -0
  235. package/docs/builtins.mdx +156 -0
  236. package/docs/bus.mdx +90 -0
  237. package/docs/declaring.mdx +85 -0
  238. package/docs/effects.mdx +49 -0
  239. package/docs/overview.mdx +76 -0
  240. package/docs/selecting.mdx +79 -0
  241. package/docs/stored.mdx +83 -0
  242. package/docs/turns.mdx +57 -0
  243. package/package.json +28 -11
  244. package/dist/builtin/deep-recall/deep-recall.d.ts +0 -27
  245. package/dist/builtin/deep-recall/deep-recall.js +0 -45
  246. package/dist/builtin/deep-recall/index.d.ts +0 -1
  247. package/dist/builtin/deep-recall/index.js +0 -1
  248. package/dist/builtin/web-search/reply.js +0 -27
  249. package/dist/builtin/write-document/commission.js +0 -152
  250. package/dist/builtin/write-document/design.js +0 -86
  251. package/dist/builtin/write-document/http.js +0 -71
  252. package/dist/builtin/write-document/index.d.ts +0 -6
  253. package/dist/builtin/write-document/index.js +0 -4
  254. package/dist/builtin/write-document/types.js +0 -1
  255. package/dist/builtin/write-document/write-document.js +0 -142
  256. /package/dist/{builtin/write-document → agents/moa}/design.d.ts +0 -0
  257. /package/dist/{builtin/write-document → agents/moa}/http.d.ts +0 -0
  258. /package/dist/{builtin/web-search → search/sonar}/reply.d.ts +0 -0
@@ -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,2 @@
1
+ /** A page's readable text: the main content where it is marked, with menus, scripts and markup gone. */
2
+ export declare function textOf(html: string): string;
@@ -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(/&nbsp;/g," ").replace(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&quot;/g,'"').replace(/&#39;|&apos;/g,"'").replace(/[ \t]+/g," ").replace(/\s*\n\s*/g,`
3
+ `).trim()}export{a as textOf};
@@ -0,0 +1,2 @@
1
+ export { readReply } from "./reply.js";
2
+ export { type SonarOptions, sonar } from "./sonar.js";
@@ -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 @@
1
+ function a(n){const s=n.search_results??[];return s.length?s.flatMap(e=>e.url?[{url:e.url,title:e.title,date:e.date}]:[]):(n.citations??[]).map(e=>({url:e}))}function l(n,s,e,r){const o=n,c=o.choices?.[0]?.message?.content??"",u=a(o),i=u.slice(0,r),t=o.usage??{};return{answer:c.slice(0,e),results:i,model:o.model??s,truncated:c.length>e||u.length>i.length,usage:{inputTokens:t.prompt_tokens??0,outputTokens:t.completion_tokens??0,citationTokens:t.citation_tokens??0,reasoningTokens:t.reasoning_tokens??0,searchQueries:t.num_search_queries??0}}}export{l as readReply};
@@ -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,4 @@
1
+ export type { TavilyReply } from "./reply.js";
2
+ export { readReply } from "./reply.js";
3
+ export type { TavilyOptions } from "./tavily.js";
4
+ export { tavily } from "./tavily.js";
@@ -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,2 @@
1
+ export { type Replay, replaying, type Unrecorded } from "./replay.js";
2
+ export { type MemorySeed, type MemoryStore, memoryStore, type ObservedMoment, type StoredMemory, } from "./store.js";
@@ -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/bind.js CHANGED
@@ -1,64 +1 @@
1
- import { tool } from "./tool.js";
2
- export class ToolDriftError extends Error {
3
- toolName;
4
- differences;
5
- constructor(toolName, differences) {
6
- super(`Tool "${toolName}" is stored differently from the code binding it: ${differences.join("; ")}.`);
7
- this.toolName = toolName;
8
- this.differences = differences;
9
- this.name = "ToolDriftError";
10
- }
11
- }
12
- export class ToolNotStoredError extends Error {
13
- toolName;
14
- constructor(toolName) {
15
- super(`No stored tool named "${toolName}".`);
16
- this.toolName = toolName;
17
- this.name = "ToolNotStoredError";
18
- }
19
- }
20
- function drift(declared, stored) {
21
- const out = [];
22
- const byName = new Map(stored.map((a) => [a.name, a]));
23
- for (const arg of declared) {
24
- const found = byName.get(arg.name);
25
- if (!found) {
26
- out.push(`"${arg.name}" is declared here but not stored`);
27
- continue;
28
- }
29
- byName.delete(arg.name);
30
- if (found.type !== arg.type) {
31
- out.push(`"${arg.name}" is ${arg.type} here and ${found.type} stored`);
32
- }
33
- if (found.required !== arg.required) {
34
- out.push(`"${arg.name}" is ${arg.required ? "required" : "optional"} here and the reverse stored`);
35
- }
36
- }
37
- for (const name of byName.keys()) {
38
- out.push(`"${name}" is stored but not declared here`);
39
- }
40
- return out;
41
- }
42
- export async function bindTool(sg, spec, query = {}) {
43
- let stored;
44
- try {
45
- ({ tool: stored } = await sg.tools.get(spec.name, query));
46
- }
47
- catch (err) {
48
- if (err && typeof err === "object" && "status" in err && err.status === 404) {
49
- throw new ToolNotStoredError(spec.name);
50
- }
51
- throw err;
52
- }
53
- const differences = drift(spec.args, stored.args ?? []);
54
- if (differences.length)
55
- throw new ToolDriftError(spec.name, differences);
56
- return tool({
57
- name: stored.name,
58
- description: stored.description,
59
- args: spec.args,
60
- effect: stored.effect ?? undefined,
61
- pinned: stored.pinned,
62
- run: spec.run,
63
- });
64
- }
1
+ import{tool as i}from"./tool.js";class a extends Error{toolName;differences;constructor(r,t){super(`Tool "${r}" is stored differently from the code binding it: ${t.join("; ")}.`),this.toolName=r,this.differences=t,this.name="ToolDriftError"}}class d extends Error{toolName;constructor(r){super(`No stored tool named "${r}".`),this.toolName=r,this.name="ToolNotStoredError"}}function f(s,r){const t=[],o=new Map(r.map(e=>[e.name,e]));for(const e of s){const n=o.get(e.name);if(!n){t.push(`"${e.name}" is declared here but not stored`);continue}o.delete(e.name),n.type!==e.type&&t.push(`"${e.name}" is ${e.type} here and ${n.type} stored`),n.required!==e.required&&t.push(`"${e.name}" is ${e.required?"required":"optional"} here and the reverse stored`)}for(const e of o.keys())t.push(`"${e}" is stored but not declared here`);return t}async function c(s,r,t={}){let o;try{({tool:o}=await s.tools.get(r.name,t))}catch(n){throw n&&typeof n=="object"&&"status"in n&&n.status===404?new d(r.name):n}const e=f(r.args,o.args??[]);if(e.length)throw new a(r.name,e);return i({name:o.name,description:o.description,args:r.args,effect:o.effect??void 0,pinned:o.pinned,run:r.run})}export{a as ToolDriftError,d as ToolNotStoredError,c as bindTool};
@@ -1,2 +1 @@
1
- export { bindTool, ToolDriftError, ToolNotStoredError } from "./bind.js";
2
- export { tool } from "./tool.js";
1
+ import{bindTool as t,ToolDriftError as l,ToolNotStoredError as e}from"./bind.js";import{tool as T}from"./tool.js";export{l as ToolDriftError,e as ToolNotStoredError,t as bindTool,T as tool};
@@ -1,10 +1,14 @@
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;
5
5
  args: T;
6
+ aliases?: readonly string[];
6
7
  pinned?: boolean;
7
8
  effect?: Effect;
9
+ trust?: Trust;
10
+ costOf?(value: unknown): number | undefined;
11
+ needs?: readonly string[];
8
12
  run(args: ArgsOf<T>): Promise<unknown> | unknown;
9
13
  }
10
14
  /**
package/dist/tool/tool.js CHANGED
@@ -1,51 +1 @@
1
- const VALID_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
2
- const EFFECTS = ["readonly", "idempotent", "destructive"];
3
- function checkEnum(toolName, arg) {
4
- if (!arg.options?.length) {
5
- throw new Error(`Tool "${toolName}" argument "${arg.name}" is an enum with no options, so nothing can satisfy it.`);
6
- }
7
- const choices = new Set();
8
- for (const option of arg.options) {
9
- if (!option.trim()) {
10
- throw new Error(`Tool "${toolName}" argument "${arg.name}" has a blank option, which the model can pick and you cannot act on.`);
11
- }
12
- if (choices.has(option)) {
13
- throw new Error(`Tool "${toolName}" argument "${arg.name}" lists "${option}" twice.`);
14
- }
15
- choices.add(option);
16
- }
17
- }
18
- function checkArgs(toolName, args) {
19
- const seen = new Set();
20
- for (const arg of args) {
21
- if (!VALID_NAME.test(arg.name)) {
22
- throw new Error(`Tool "${toolName}" argument "${arg.name}" is not a usable name.`);
23
- }
24
- if (seen.has(arg.name)) {
25
- throw new Error(`Tool "${toolName}" declares "${arg.name}" twice.`);
26
- }
27
- seen.add(arg.name);
28
- if (arg.type === "enum")
29
- checkEnum(toolName, arg);
30
- }
31
- }
32
- export function tool(spec) {
33
- if (!VALID_NAME.test(spec.name)) {
34
- throw new Error(`Tool name "${spec.name}" must be letters, digits and underscores, starting with a letter.`);
35
- }
36
- if (!spec.description.trim()) {
37
- throw new Error(`Tool "${spec.name}" has no description. It is the only thing the model reads to decide whether to call it.`);
38
- }
39
- checkArgs(spec.name, spec.args);
40
- if (spec.effect !== undefined && !EFFECTS.includes(spec.effect)) {
41
- throw new Error(`Tool "${spec.name}" declares effect "${spec.effect}", which is not one of ${EFFECTS.join(", ")}.`);
42
- }
43
- return {
44
- name: spec.name,
45
- description: spec.description,
46
- args: spec.args.map((a) => ({ ...a, options: a.options ? [...a.options] : undefined })),
47
- pinned: spec.pinned,
48
- ...(spec.effect ? { effect: spec.effect } : {}),
49
- run: spec.run,
50
- };
51
- }
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};
@@ -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";
@@ -1 +0,0 @@
1
- export {};
@@ -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
  }
@@ -1 +0,0 @@
1
- export {};
@@ -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
  *
@@ -82,6 +98,14 @@ export interface Tool<Args = Record<string, unknown>> {
82
98
  */
83
99
  description: string;
84
100
  args: FieldSpec[];
101
+ /**
102
+ * Extra wording selection matches on, never sent to the model.
103
+ *
104
+ * Lexical ranking cannot reach a synonym: a question about money back scores
105
+ * zero against a tool called `refund`, whatever the description says. This is
106
+ * where the words a caller actually types are declared.
107
+ */
108
+ aliases?: string[];
85
109
  /**
86
110
  * Offered whatever selection decides — an escalation path, a refusal, where
87
111
  * losing a similarity search is not an acceptable outcome.
@@ -89,6 +113,51 @@ export interface Tool<Args = Record<string, unknown>> {
89
113
  pinned?: boolean;
90
114
  /** What calling it does outside the process. Unset means unknown. */
91
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[];
92
161
  run(args: Args): Promise<unknown> | unknown;
93
162
  }
94
163
  /** A tool as the model sees it: no implementation, no pin. */
@@ -96,6 +165,11 @@ export interface ToolDeclaration {
96
165
  name: string;
97
166
  description: string;
98
167
  args: FieldSpec[];
168
+ /**
169
+ * Carried so a nested bus can rank on it. Dropped by every provider shape,
170
+ * exactly as `effect` is.
171
+ */
172
+ aliases?: string[];
99
173
  /**
100
174
  * Carried so a caller can gate on it. Dropped by every provider shape — no
101
175
  * API has a field for it, and an unknown key is rejected or ignored.
@@ -1 +0,0 @@
1
- export {};
@@ -1,3 +1 @@
1
- export { toJsonSchema, } from "./json-schema.js";
2
- export { toMarkdown } from "./markdown.js";
3
- export { toAnthropic, toOpenAI, } from "./providers.js";
1
+ import{toJsonSchema as t}from"./json-schema.js";import{toMarkdown as p}from"./markdown.js";import{toAnthropic as n,toOpenAI as f}from"./providers.js";export{n as toAnthropic,t as toJsonSchema,p as toMarkdown,f as toOpenAI};
@@ -1,65 +1 @@
1
- export const JSON_TYPE = {
2
- string: "string",
3
- text: "string",
4
- number: "number",
5
- boolean: "boolean",
6
- enum: "string",
7
- list: "array",
8
- json: "object",
9
- };
10
- function coerceDefault(field) {
11
- const raw = field.default;
12
- if (raw === undefined)
13
- return undefined;
14
- switch (field.type) {
15
- case "number": {
16
- const n = Number(raw);
17
- return Number.isFinite(n) ? n : undefined;
18
- }
19
- case "boolean": {
20
- const t = raw.trim().toLowerCase();
21
- if (t === "true" || t === field.trueText?.trim().toLowerCase())
22
- return true;
23
- if (t === "false" || t === field.falseText?.trim().toLowerCase())
24
- return false;
25
- return undefined;
26
- }
27
- case "enum":
28
- return field.options?.includes(raw) ? raw : undefined;
29
- case "list":
30
- case "json":
31
- return undefined;
32
- default:
33
- return raw;
34
- }
35
- }
36
- export function toJsonSchema(args) {
37
- const properties = {};
38
- for (const field of args) {
39
- const property = { type: JSON_TYPE[field.type] };
40
- if (field.description)
41
- property.description = field.description;
42
- if (field.type === "enum" && field.options?.length)
43
- property.enum = [...field.options];
44
- if (field.type === "list")
45
- property.items = { type: "string" };
46
- if (field.type === "number") {
47
- if (field.min !== undefined)
48
- property.minimum = field.min;
49
- if (field.max !== undefined)
50
- property.maximum = field.max;
51
- }
52
- if ((field.type === "string" || field.type === "text") && field.maxLength !== undefined) {
53
- property.maxLength = field.maxLength;
54
- }
55
- const fallback = coerceDefault(field);
56
- if (fallback !== undefined)
57
- property.default = fallback;
58
- properties[field.name] = property;
59
- }
60
- return {
61
- type: "object",
62
- properties,
63
- required: args.filter((a) => a.required).map((a) => a.name),
64
- };
65
- }
1
+ const o={string:"string",text:"string",number:"number",boolean:"boolean",enum:"string",list:"array",json:"object"};function u(t){const n=t.default;if(n!==void 0)switch(t.type){case"number":{const e=Number(n);return Number.isFinite(e)?e:void 0}case"boolean":{const e=n.trim().toLowerCase();return e==="true"||e===t.trueText?.trim().toLowerCase()?!0:e==="false"||e===t.falseText?.trim().toLowerCase()?!1:void 0}case"enum":return t.options?.includes(n)?n:void 0;case"list":case"json":return;default:return n}}function s(t){const n={};for(const e of t){const r={type:o[e.type]};e.description&&(r.description=e.description),e.type==="enum"&&e.options?.length&&(r.enum=[...e.options]),e.type==="list"&&(r.items={type:"string"}),e.type==="number"&&(e.min!==void 0&&(r.minimum=e.min),e.max!==void 0&&(r.maximum=e.max)),(e.type==="string"||e.type==="text")&&e.maxLength!==void 0&&(r.maxLength=e.maxLength);const i=u(e);i!==void 0&&(r.default=i),n[e.name]=r}return{type:"object",properties:n,required:t.filter(e=>e.required).map(e=>e.name)}}export{o as JSON_TYPE,s as toJsonSchema};
@@ -2,8 +2,10 @@ import type { ToolDeclaration } from "../types/index.js";
2
2
  /**
3
3
  * Declarations as markdown, for a model with no tools API.
4
4
  *
5
- * About a quarter cheaper than the JSON, since JSON Schema is mostly
6
- * scaffolding — but you give up structured tool calls and parse intent out of
7
- * prose instead. Only worth it where there is no native `tools` array.
5
+ * A third to two fifths cheaper than the JSON, since JSON Schema is mostly
6
+ * scaffolding — 31% under the Anthropic shape on a bus of nine tools, 38% on a
7
+ * bus of three, and more again against OpenAI's extra envelope. But you give up
8
+ * structured tool calls and parse intent out of prose instead. Only worth it
9
+ * where there is no native `tools` array.
8
10
  */
9
11
  export declare function toMarkdown(decls: ToolDeclaration[], heading?: string): string;