@spendgraph/tools 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/README.md +3 -2
  2. package/dist/builtin/calculate/calculate.d.ts +2 -0
  3. package/dist/builtin/calculate/calculate.js +1 -1
  4. package/dist/builtin/calculate/index.d.ts +2 -1
  5. package/dist/builtin/calculate/index.js +1 -1
  6. package/dist/builtin/code-exec/code-exec.d.ts +14 -0
  7. package/dist/builtin/date/date.d.ts +1 -1
  8. package/dist/builtin/date/date.js +1 -1
  9. package/dist/builtin/date/index.d.ts +1 -0
  10. package/dist/builtin/date/index.js +1 -1
  11. package/dist/builtin/date/shift.d.ts +1 -1
  12. package/dist/builtin/date/shift.js +1 -1
  13. package/dist/builtin/date/words.d.ts +13 -0
  14. package/dist/builtin/date/words.js +1 -0
  15. package/dist/builtin/decline/decline.d.ts +49 -0
  16. package/dist/builtin/decline/decline.js +1 -0
  17. package/dist/builtin/decline/index.d.ts +1 -0
  18. package/dist/builtin/decline/index.js +1 -0
  19. package/dist/builtin/escalate/escalate.d.ts +19 -0
  20. package/dist/builtin/escalate/escalate.js +1 -1
  21. package/dist/builtin/escalate/index.d.ts +1 -1
  22. package/dist/builtin/escalate/index.js +1 -1
  23. package/dist/builtin/extract/extract.d.ts +25 -0
  24. package/dist/builtin/extract/extract.js +1 -0
  25. package/dist/builtin/extract/index.d.ts +3 -0
  26. package/dist/builtin/extract/index.js +1 -0
  27. package/dist/builtin/extract/kinds.d.ts +6 -0
  28. package/dist/builtin/extract/kinds.js +1 -0
  29. package/dist/builtin/extract/readers.d.ts +7 -0
  30. package/dist/builtin/extract/readers.js +9 -0
  31. package/dist/builtin/finish/finish.d.ts +4 -0
  32. package/dist/builtin/finish/finish.js +1 -1
  33. package/dist/builtin/http/allow.d.ts +2 -0
  34. package/dist/builtin/http/allow.js +1 -1
  35. package/dist/builtin/http/hops.d.ts +10 -0
  36. package/dist/builtin/http/hops.js +1 -0
  37. package/dist/builtin/http/http.d.ts +9 -1
  38. package/dist/builtin/http/http.js +1 -1
  39. package/dist/builtin/http/index.d.ts +2 -0
  40. package/dist/builtin/http/index.js +1 -1
  41. package/dist/builtin/http/retry-after.d.ts +14 -0
  42. package/dist/builtin/http/retry-after.js +1 -0
  43. package/dist/builtin/index.d.ts +11 -6
  44. package/dist/builtin/index.js +1 -1
  45. package/dist/builtin/json/index.d.ts +1 -0
  46. package/dist/builtin/json/index.js +1 -1
  47. package/dist/builtin/json/json.js +1 -1
  48. package/dist/builtin/json/parse.d.ts +15 -0
  49. package/dist/builtin/json/parse.js +1 -0
  50. package/dist/builtin/notes/index.d.ts +1 -0
  51. package/dist/builtin/notes/index.js +1 -0
  52. package/dist/builtin/notes/notes.d.ts +45 -0
  53. package/dist/builtin/notes/notes.js +2 -0
  54. package/dist/builtin/openalex/api.d.ts +26 -0
  55. package/dist/builtin/openalex/api.js +1 -0
  56. package/dist/builtin/openalex/index.d.ts +3 -0
  57. package/dist/builtin/openalex/index.js +1 -0
  58. package/dist/builtin/openalex/openalex.d.ts +36 -0
  59. package/dist/builtin/openalex/openalex.js +1 -0
  60. package/dist/builtin/paper-search/index.d.ts +1 -0
  61. package/dist/builtin/paper-search/index.js +1 -0
  62. package/dist/builtin/paper-search/paper-search.d.ts +38 -0
  63. package/dist/builtin/paper-search/paper-search.js +3 -0
  64. package/dist/builtin/polite.d.ts +15 -0
  65. package/dist/builtin/polite.js +1 -0
  66. package/dist/builtin/publish/index.d.ts +1 -0
  67. package/dist/builtin/publish/index.js +1 -0
  68. package/dist/builtin/publish/publish.d.ts +46 -0
  69. package/dist/builtin/publish/publish.js +1 -0
  70. package/dist/builtin/web-search/web-search.d.ts +10 -35
  71. package/dist/builtin/web-search/web-search.js +1 -1
  72. package/dist/builtin/wikidata/api.d.ts +40 -0
  73. package/dist/builtin/wikidata/api.js +1 -0
  74. package/dist/builtin/wikidata/index.d.ts +5 -0
  75. package/dist/builtin/wikidata/index.js +1 -0
  76. package/dist/builtin/wikidata/render.d.ts +13 -0
  77. package/dist/builtin/wikidata/render.js +2 -0
  78. package/dist/builtin/wikidata/wikidata.d.ts +23 -0
  79. package/dist/builtin/wikidata/wikidata.js +1 -0
  80. package/dist/bus/bus.d.ts +23 -1
  81. package/dist/bus/bus.js +1 -1
  82. package/dist/bus/group.d.ts +14 -1
  83. package/dist/bus/group.js +1 -1
  84. package/dist/bus/index.d.ts +4 -1
  85. package/dist/bus/index.js +1 -1
  86. package/dist/bus/invoke.d.ts +1 -1
  87. package/dist/bus/invoke.js +3 -1
  88. package/dist/bus/namespace.d.ts +21 -0
  89. package/dist/bus/namespace.js +1 -0
  90. package/dist/bus/once.d.ts +32 -0
  91. package/dist/bus/once.js +1 -0
  92. package/dist/bus/only.d.ts +29 -0
  93. package/dist/bus/options.d.ts +31 -0
  94. package/dist/bus/output.d.ts +0 -17
  95. package/dist/bus/output.js +2 -2
  96. package/dist/bus/select.d.ts +0 -16
  97. package/dist/bus/select.js +1 -1
  98. package/dist/bus/spent.d.ts +16 -0
  99. package/dist/bus/spent.js +1 -0
  100. package/dist/bus/trace.d.ts +14 -1
  101. package/dist/bus/trace.js +1 -1
  102. package/dist/index.d.ts +6 -4
  103. package/dist/index.js +1 -1
  104. package/dist/internals.d.ts +8 -1
  105. package/dist/internals.js +1 -1
  106. package/dist/memories/client.d.ts +91 -0
  107. package/dist/memories/client.js +0 -0
  108. package/dist/memories/context.d.ts +5 -0
  109. package/dist/memories/context.js +1 -0
  110. package/dist/{builtin → memories}/deep-recall/deep-recall.d.ts +3 -0
  111. package/dist/memories/deep-recall/deep-recall.js +1 -0
  112. package/dist/memories/explore/explore.d.ts +18 -0
  113. package/dist/memories/explore/explore.js +7 -0
  114. package/dist/memories/explore/subgraph.d.ts +12 -0
  115. package/dist/memories/explore/subgraph.js +3 -0
  116. package/dist/memories/index.d.ts +30 -0
  117. package/dist/memories/index.js +1 -0
  118. package/dist/memories/observe/observe.d.ts +7 -0
  119. package/dist/memories/observe/observe.js +1 -0
  120. package/dist/memories/recall/recall.d.ts +42 -0
  121. package/dist/memories/recall/recall.js +1 -0
  122. package/dist/memories/recall/render.d.ts +5 -0
  123. package/dist/memories/recall/render.js +3 -0
  124. package/dist/memories/remember/remember.d.ts +49 -0
  125. package/dist/memories/remember/remember.js +1 -0
  126. package/dist/papers/consensus/consensus.d.ts +12 -0
  127. package/dist/papers/consensus/consensus.js +1 -0
  128. package/dist/papers/consensus/index.d.ts +4 -0
  129. package/dist/papers/consensus/index.js +1 -0
  130. package/dist/papers/consensus/reply.d.ts +24 -0
  131. package/dist/papers/consensus/reply.js +1 -0
  132. package/dist/papers/every.d.ts +6 -0
  133. package/dist/papers/every.js +1 -0
  134. package/dist/papers/index.d.ts +11 -0
  135. package/dist/papers/index.js +1 -0
  136. package/dist/papers/openalex/index.d.ts +2 -0
  137. package/dist/papers/openalex/index.js +1 -0
  138. package/dist/papers/openalex/openalex.d.ts +8 -0
  139. package/dist/papers/openalex/openalex.js +1 -0
  140. package/dist/papers/papers.d.ts +54 -0
  141. package/dist/papers/papers.js +1 -0
  142. package/dist/papers/render.d.ts +2 -0
  143. package/dist/papers/render.js +4 -0
  144. package/dist/papers/semantic-scholar/index.d.ts +4 -0
  145. package/dist/papers/semantic-scholar/index.js +1 -0
  146. package/dist/papers/semantic-scholar/reply.d.ts +29 -0
  147. package/dist/papers/semantic-scholar/reply.js +1 -0
  148. package/dist/papers/semantic-scholar/semantic-scholar.d.ts +8 -0
  149. package/dist/papers/semantic-scholar/semantic-scholar.js +1 -0
  150. package/dist/search/every.d.ts +6 -0
  151. package/dist/search/every.js +4 -0
  152. package/dist/search/index.d.ts +7 -0
  153. package/dist/search/index.js +1 -0
  154. package/dist/search/retry.d.ts +6 -0
  155. package/dist/search/retry.js +1 -0
  156. package/dist/search/search.d.ts +69 -0
  157. package/dist/search/search.js +1 -0
  158. package/dist/search/searxng/index.d.ts +2 -0
  159. package/dist/search/searxng/index.js +1 -0
  160. package/dist/search/searxng/searxng.d.ts +14 -0
  161. package/dist/search/searxng/searxng.js +5 -0
  162. package/dist/search/searxng/text.d.ts +2 -0
  163. package/dist/search/searxng/text.js +3 -0
  164. package/dist/search/sonar/index.d.ts +2 -0
  165. package/dist/search/sonar/index.js +1 -0
  166. package/dist/search/sonar/sonar.d.ts +13 -0
  167. package/dist/search/sonar/sonar.js +1 -0
  168. package/dist/search/tavily/index.d.ts +4 -0
  169. package/dist/search/tavily/index.js +1 -0
  170. package/dist/search/tavily/reply.d.ts +22 -0
  171. package/dist/search/tavily/reply.js +5 -0
  172. package/dist/search/tavily/tavily.d.ts +14 -0
  173. package/dist/search/tavily/tavily.js +1 -0
  174. package/dist/testing/index.d.ts +2 -0
  175. package/dist/testing/index.js +1 -0
  176. package/dist/testing/replay.d.ts +55 -0
  177. package/dist/testing/replay.js +1 -0
  178. package/dist/testing/store.d.ts +35 -0
  179. package/dist/testing/store.js +4 -0
  180. package/dist/tool/tool.d.ts +8 -1
  181. package/dist/tool/tool.js +1 -1
  182. package/dist/types/index.d.ts +1 -1
  183. package/dist/types/result.d.ts +38 -0
  184. package/dist/types/tool.d.ts +74 -0
  185. package/docs/bogus.mdx +85 -0
  186. package/docs/builtins.mdx +217 -0
  187. package/docs/bus.mdx +90 -0
  188. package/docs/declaring.mdx +85 -0
  189. package/docs/effects.mdx +49 -0
  190. package/docs/overview.mdx +76 -0
  191. package/docs/selecting.mdx +79 -0
  192. package/docs/stored.mdx +83 -0
  193. package/docs/turns.mdx +57 -0
  194. package/package.json +28 -9
  195. package/dist/builtin/deep-recall/deep-recall.js +0 -1
  196. package/dist/builtin/write-document/commission.d.ts +0 -56
  197. package/dist/builtin/write-document/commission.js +0 -13
  198. package/dist/builtin/write-document/design.d.ts +0 -49
  199. package/dist/builtin/write-document/design.js +0 -5
  200. package/dist/builtin/write-document/http.d.ts +0 -34
  201. package/dist/builtin/write-document/http.js +0 -1
  202. package/dist/builtin/write-document/index.d.ts +0 -6
  203. package/dist/builtin/write-document/index.js +0 -1
  204. package/dist/builtin/write-document/types.d.ts +0 -33
  205. package/dist/builtin/write-document/write-document.d.ts +0 -69
  206. package/dist/builtin/write-document/write-document.js +0 -1
  207. /package/dist/{builtin/write-document/types.js → bus/only.js} +0 -0
  208. /package/dist/{builtin → memories}/deep-recall/index.d.ts +0 -0
  209. /package/dist/{builtin → memories}/deep-recall/index.js +0 -0
  210. /package/dist/{builtin/web-search → search/sonar}/reply.d.ts +0 -0
  211. /package/dist/{builtin/web-search → search/sonar}/reply.js +0 -0
package/README.md CHANGED
@@ -28,7 +28,8 @@ const turn = bus.trace(question);
28
28
  has said anything, and more tools makes a model worse at picking.
29
29
  - **Effects**, so a gate can refuse what it must not retry — and name the tools
30
30
  nobody labelled.
31
- - **Twelve builtins and twelve mocks**, none registered for you.
31
+ - **Twenty-two builtins and twelve mocks**, none registered for you. Four of them
32
+ are one memory set, with an in-process store for testing it offline.
32
33
 
33
34
  ## Docs
34
35
 
@@ -41,7 +42,7 @@ const turn = bus.trace(question);
41
42
  | [The bus](https://spendgraph.locusgraph.com/docs/tools/bus) | Holds, picks, converts, runs — and nests |
42
43
  | [Selecting](https://spendgraph.locusgraph.com/docs/tools/selecting) | The shortlist, and why it is a shortlist |
43
44
  | [A turn](https://spendgraph.locusgraph.com/docs/tools/turns) | Offered, called, and what the rollout keeps |
44
- | [Builtins](https://spendgraph.locusgraph.com/docs/tools/builtins) | Twelve factories, none registered for you |
45
+ | [Builtins](https://spendgraph.locusgraph.com/docs/tools/builtins) | Sixteen factories, none registered for you |
45
46
  | [Bogus tools](https://spendgraph.locusgraph.com/docs/tools/bogus) | Twelve mocks for testing the pipeline, not the provider |
46
47
 
47
48
  ## License
@@ -1,3 +1,5 @@
1
+ /** The longest sum inside a sentence, for a caller that sent the question it was asked. */
2
+ export declare function sumIn(text: string): string | null;
1
3
  /**
2
4
  * Arithmetic, because a model does it from memory and is confidently wrong.
3
5
  *
@@ -1 +1 @@
1
- import{tool as r}from"../../tool/index.js";import{exact as t}from"./exact.js";import{evaluate as o}from"./parse.js";function i(){return r({name:"calculate",effect:"readonly",description:"Works out an arithmetic expression exactly. Use it for any sum that matters \u2014 totals, percentages, differences, rates. Supports + - * / % ^, brackets, and sqrt, abs, round, floor, ceil, ln, log.",args:[{name:"expression",type:"string",required:!0,maxLength:500,description:"For example (1250 * 0.2) + 99, or sqrt(144)."}],run:({expression:e})=>t(o(e))})}export{i as calculate};
1
+ import{tool as a}from"../../tool/index.js";import{exact as n}from"./exact.js";import{evaluate as o}from"./parse.js";const c=/[0-9+\-*/%^().\s]+/g;function u(r){return(r.match(c)??[]).map(t=>t.trim()).filter(t=>/\d/.test(t)&&/[+\-*/%^]/.test(t)).sort((t,s)=>s.length-t.length)[0]??null}function f(){return a({name:"calculate",effect:"readonly",description:"Works out an arithmetic expression exactly. Use it for any sum that matters \u2014 totals, percentages, differences, rates. Supports + - * / % ^, brackets, and sqrt, abs, round, floor, ceil, ln, log.",args:[{name:"expression",type:"string",required:!0,maxLength:500,description:"For example (1250 * 0.2) + 99, or sqrt(144)."}],run:({expression:r})=>{try{return n(o(r))}catch(e){const t=u(r);if(t===null)throw new Error("Send arithmetic only, such as 812 * 0.2611. Words, units and currency signs are not understood.");if(t===r.trim())throw e;return n(o(t))}}})}export{f as calculate,u as sumIn};
@@ -1,2 +1,3 @@
1
- export { calculate } from "./calculate.js";
1
+ export { calculate, sumIn } from "./calculate.js";
2
+ export { exact } from "./exact.js";
2
3
  export { evaluate } from "./parse.js";
@@ -1 +1 @@
1
- import{calculate as o}from"./calculate.js";import{evaluate as t}from"./parse.js";export{o as calculate,t as evaluate};
1
+ import{calculate as r,sumIn as t}from"./calculate.js";import{exact as m}from"./exact.js";import{evaluate as c}from"./parse.js";export{r as calculate,c as evaluate,m as exact,t as sumIn};
@@ -19,6 +19,20 @@ export interface CodeExecOptions {
19
19
  timeoutMs?: number;
20
20
  maxChars?: number;
21
21
  }
22
+ /**
23
+ * A tool that runs a short program the model wrote, in a sandbox you supply.
24
+ *
25
+ * Cheaper than calling tools one at a time when a task needs several: the model
26
+ * writes the orchestration once, and only what the program prints comes back, so
27
+ * intermediate results never reach the context at all.
28
+ *
29
+ * **Whatever the sandbox reaches, the model reaches, and no hook sees it.** A
30
+ * `beforeTool` guard runs on the call to `code_exec` and on nothing the program
31
+ * does inside it — a tool exposed to the sandbox is callable without the effect
32
+ * check, the argument rail or the approval that guards it on the bus. The
33
+ * sandbox is the boundary, and it is yours: what goes in it is the whole of the
34
+ * decision about what a model-written program may do.
35
+ */
22
36
  export declare function codeExec(opts: CodeExecOptions): import("../../index.js").Tool<import("../../index.js").ArgsOf<readonly [{
23
37
  readonly name: "code";
24
38
  readonly type: "text";
@@ -18,7 +18,7 @@ export declare function dateMath(): import("../../index.js").Tool<import("../../
18
18
  readonly name: "unit";
19
19
  readonly type: "enum";
20
20
  readonly required: true;
21
- readonly options: readonly ["seconds", "minutes", "hours", "days", "weeks", "months", "years"];
21
+ readonly options: readonly ["seconds", "minutes", "hours", "days", "weeks", "months", "years", "weekdays"];
22
22
  }, {
23
23
  readonly name: "amount";
24
24
  readonly type: "number";
@@ -1 +1 @@
1
- import{tool as i}from"../../tool/index.js";import{between as f,parseDate as d,shift as u}from"./shift.js";const c=["seconds","minutes","hours","days","weeks","months","years"];function h(){return i({name:"date_math",effect:"readonly",description:"Works out a date exactly. `add` and `subtract` move a date by an amount of a unit; `difference` says how far apart two dates are in whole units. Use it for deadlines, notice periods, ages and 'how long ago' \u2014 never work a date out yourself.",args:[{name:"operation",type:"enum",required:!0,options:["add","subtract","difference"]},{name:"date",type:"string",required:!0,description:"ISO, as in 2026-03-31."},{name:"unit",type:"enum",required:!0,options:[...c]},{name:"amount",type:"number",required:!1,description:"For add and subtract."},{name:"to",type:"string",required:!1,description:"The other date, for difference."}],run:({operation:e,date:s,unit:t,amount:r,to:n})=>{const a=d(s,"date");if(e==="difference"){if(!n)throw new Error("difference needs a second date in `to`.");return{unit:t,difference:f(a,d(n,"to"),t)}}if(r===void 0)throw new Error(`${e} needs an \`amount\`.`);const o=u(a,e==="subtract"?-r:r,t);return{iso:o.toISOString(),date:o.toISOString().slice(0,10)}}})}export{h as dateMath};
1
+ import{tool as i}from"../../tool/index.js";import{between as u,parseDate as d,shift as f}from"./shift.js";const y=["seconds","minutes","hours","days","weeks","months","years","weekdays"],c=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];function h(){return i({name:"date_math",effect:"readonly",description:"Works out a date exactly. `add` and `subtract` move a date by an amount of a unit; `difference` says how far apart two dates are in whole units. `weekdays` is Monday to Friday, for anything written in working days; it knows nothing about holidays. Use it for deadlines, notice periods, ages and 'how long ago' \u2014 never work a date out yourself.",args:[{name:"operation",type:"enum",required:!0,options:["add","subtract","difference"]},{name:"date",type:"string",required:!0,description:"ISO, as in 2026-03-31."},{name:"unit",type:"enum",required:!0,options:[...y]},{name:"amount",type:"number",required:!1,description:"For add and subtract."},{name:"to",type:"string",required:!1,description:"The other date, for difference."}],run:({operation:e,date:s,unit:t,amount:r,to:n})=>{const o=d(s,"date");if(e==="difference"){if(!n)throw new Error("difference needs a second date in `to`.");return{unit:t,difference:u(o,d(n,"to"),t)}}if(r===void 0)throw new Error(`${e} needs an \`amount\`.`);const a=f(o,e==="subtract"?-r:r,t);return{iso:a.toISOString(),date:a.toISOString().slice(0,10),weekday:c[a.getUTCDay()]}}})}export{h as dateMath};
@@ -1,2 +1,3 @@
1
1
  export { dateMath } from "./date.js";
2
2
  export { between, parseDate, shift, type Unit } from "./shift.js";
3
+ export { dates } from "./words.js";
@@ -1 +1 @@
1
- import{dateMath as r}from"./date.js";import{between as o,parseDate as f,shift as p}from"./shift.js";export{o as between,r as dateMath,f as parseDate,p as shift};
1
+ import{dateMath as r}from"./date.js";import{between as a,parseDate as f,shift as p}from"./shift.js";import{dates as s}from"./words.js";export{a as between,r as dateMath,s as dates,f as parseDate,p as shift};
@@ -1,4 +1,4 @@
1
- export type Unit = "seconds" | "minutes" | "hours" | "days" | "weeks" | "months" | "years";
1
+ export type Unit = "seconds" | "minutes" | "hours" | "days" | "weeks" | "months" | "years" | "weekdays";
2
2
  /** Parses an ISO instant, or says which argument was not one. */
3
3
  export declare function parseDate(value: string, label: string): Date;
4
4
  /** Moves an instant by a whole number of units. Negative goes backwards. */
@@ -1 +1 @@
1
- const o={seconds:1e3,minutes:6e4,hours:36e5,days:864e5,weeks:6048e5};function i(e,n){const t=new Date(e);if(!Number.isFinite(t.getTime()))throw new Error(`${n} "${e}" is not a date. Use an ISO instant, as in 2026-03-31.`);return t}function a(e,n){const t=e.getUTCDate(),s=new Date(e.getTime());s.setUTCDate(1),s.setUTCMonth(s.getUTCMonth()+n);const r=new Date(Date.UTC(s.getUTCFullYear(),s.getUTCMonth()+1,0)).getUTCDate();return s.setUTCDate(Math.min(t,r)),s}function h(e,n,t){if(!Number.isInteger(n))throw new Error("amount must be a whole number.");if(t==="months")return a(e,n);if(t==="years")return a(e,n*12);const s=o[t];if(!s)throw new Error(`"${t}" is not a unit this tool knows.`);return new Date(e.getTime()+n*s)}function T(e,n,t){if(t==="months"||t==="years"){const r=(n.getUTCFullYear()-e.getUTCFullYear())*12+(n.getUTCMonth()-e.getUTCMonth())-(n.getUTCDate()<e.getUTCDate()?1:0);return t==="years"?Math.trunc(r/12):r}const s=o[t];if(!s)throw new Error(`"${t}" is not a unit this tool knows.`);return Math.trunc((n.getTime()-e.getTime())/s)}export{T as between,i as parseDate,h as shift};
1
+ const T={seconds:1e3,minutes:6e4,hours:36e5,days:864e5,weeks:6048e5};function D(e,t){const n=new Date(e);if(!Number.isFinite(n.getTime()))throw new Error(`${t} "${e}" is not a date. Use an ISO instant, as in 2026-03-31.`);return n}function w(e,t){const n=e.getUTCDate(),s=new Date(e.getTime());s.setUTCDate(1),s.setUTCMonth(s.getUTCMonth()+t);const a=new Date(Date.UTC(s.getUTCFullYear(),s.getUTCMonth()+1,0)).getUTCDate();return s.setUTCDate(Math.min(n,a)),s}const i=864e5,g=e=>e.getUTCDay()===0||e.getUTCDay()===6;function h(e,t){const n=t<0?-i:i;let s=new Date(e.getTime()),a=Math.abs(t);for(;a>0;)s=new Date(s.getTime()+n),g(s)||(a-=1);return s}function l(e,t,n){if(!Number.isInteger(t))throw new Error("amount must be a whole number.");if(n==="months")return w(e,t);if(n==="years")return w(e,t*12);if(n==="weekdays")return h(e,t);const s=T[n];if(!s)throw new Error(`"${n}" is not a unit this tool knows.`);return new Date(e.getTime()+t*s)}function U(e,t,n){if(n==="months"||n==="years"){const a=(t.getUTCFullYear()-e.getUTCFullYear())*12+(t.getUTCMonth()-e.getUTCMonth())-(t.getUTCDate()<e.getUTCDate()?1:0);return n==="years"?Math.trunc(a/12):a}if(n==="weekdays")return c(e,t);const s=T[n];if(!s)throw new Error(`"${n}" is not a unit this tool knows.`);return Math.trunc((t.getTime()-e.getTime())/s)}function c(e,t){const n=t.getTime()<e.getTime(),[s,a]=n?[t,e]:[e,t];let o=0,r=new Date(s.getTime());for(;r.getTime()+i<=a.getTime();)r=new Date(r.getTime()+i),g(r)||(o+=1);return n?-o:o}export{U as between,D as parseDate,l as shift};
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `date_math` asked in words, for a caller that can only send a question.
3
+ *
4
+ * Three forms are read — a span between two dates, and a date moved forward
5
+ * or back — with dates as ISO. Anything else is refused with the forms named,
6
+ * so a miss is recorded rather than a date guessed at.
7
+ */
8
+ export declare function dates(): import("../../index.js").Tool<import("../../index.js").ArgsOf<readonly [{
9
+ readonly name: "question";
10
+ readonly type: "string";
11
+ readonly required: true;
12
+ readonly maxLength: 2000;
13
+ }]>>;
@@ -0,0 +1 @@
1
+ import{tool as f}from"../../tool/index.js";import{dateMath as h}from"./date.js";const a=String.raw`(\d{4}-\d{2}-\d{2}(?:T[\d:.]+Z?)?)`,m=String.raw`(-?\d+(?:\.\d+)?)`,r=String.raw`(seconds?|minutes?|hours?|days?|weeks?|months?|years?)`,w=new RegExp(`${r}\\s+(?:from|between)\\s+${a}\\s+(?:to|and)\\s+${a}`,"i"),p=new RegExp(`${m}\\s+${r}\\s+after\\s+${a}`,"i"),E=new RegExp(`${m}\\s+${r}\\s+before\\s+${a}`,"i"),u="'days from 2026-03-01 to 2026-09-09', '30 days after 2026-03-01', or '2 months before 2026-03-01'",i=t=>(t.endsWith("s")?t:`${t}s`).toLowerCase();function $(t){const o=t.match(w);if(o){const[,e,n,s]=o;return{operation:"difference",unit:i(e),date:n,to:s}}const d=t.match(p);if(d){const[,e,n,s]=d;return{operation:"add",amount:Number(e),unit:i(n),date:s}}const c=t.match(E);if(c){const[,e,n,s]=c;return{operation:"subtract",amount:Number(e),unit:i(n),date:s}}throw new Error(`Ask date_math as one of ${u}, with dates as YYYY-MM-DD.`)}function b(){const t=h();return f({name:"date_math",description:`Date arithmetic, exact: how far apart two dates are, or a date moved by an amount. Ask as ${u}. Units: seconds, minutes, hours, days, weeks, months, years.`,effect:"readonly",args:[{name:"question",type:"string",required:!0,maxLength:2e3}],run:({question:o})=>t.run($(o))})}export{b as dates};
@@ -0,0 +1,49 @@
1
+ import type { ToolResult } from "../../types/index.js";
2
+ /** Why an answer was not given. */
3
+ export type Unanswerable = "unknowable" | "not_covered" | "out_of_scope" | "unsafe";
4
+ /** What `decline` returns, so a loop can tell it from an answer. */
5
+ export interface Declined {
6
+ declined: true;
7
+ because: Unanswerable;
8
+ reason: string;
9
+ }
10
+ /**
11
+ * True when this result is the model saying it will not answer.
12
+ *
13
+ * The counterpart to `isFinished`. Both end a run and they are opposite
14
+ * outcomes, so a caller that reads only "the loop finished" cannot tell a
15
+ * question answered from a question refused — and a refusal recorded as an
16
+ * answer is a support queue that looks healthy and a customer who got nothing.
17
+ */
18
+ export declare function isDeclined(result: ToolResult): boolean;
19
+ /** Reads the refusal out of a result, or null if it is not one. */
20
+ export declare function declinedFor(result: ToolResult): Declined | null;
21
+ /**
22
+ * Say the question cannot be answered, rather than answering it anyway.
23
+ *
24
+ * The third thing an agent can do, beside answering and asking. Asking only
25
+ * helps where the missing piece is the person's to supply: "which subscription"
26
+ * has an answer they know, and "what will the base rate be next March" has no
27
+ * answer anybody knows. A model with only `finish` and a clarifying question
28
+ * handles the second by guessing or by asking something that cannot help.
29
+ *
30
+ * `because` is a small fixed set rather than free text, so the rate of each is
31
+ * countable. `unknowable` and `not_covered` want opposite fixes — one is the
32
+ * world, the other is your records — and a single "declined" number cannot tell
33
+ * you which you have.
34
+ *
35
+ * Pinned for the reason `finish` is: it is how a run ends, not a tool competing
36
+ * to be relevant, and a turn whose shortlist dropped it has no way to say no.
37
+ */
38
+ export declare function decline(): import("../../index.js").Tool<import("../../index.js").ArgsOf<readonly [{
39
+ readonly name: "because";
40
+ readonly type: "enum";
41
+ readonly required: true;
42
+ readonly options: Unanswerable[];
43
+ }, {
44
+ readonly name: "reason";
45
+ readonly type: "text";
46
+ readonly required: true;
47
+ readonly maxLength: 2000;
48
+ readonly description: "One sentence the person who asked can read.";
49
+ }]>>;
@@ -0,0 +1 @@
1
+ import{tool as t}from"../../tool/index.js";const o=["unknowable","not_covered","out_of_scope","unsafe"];function r(e){if(e.name!=="decline"||e.status!=="completed")return!1;try{return JSON.parse(e.output).declined===!0}catch{return!1}}function u(e){return r(e)?JSON.parse(e.output):null}function i(){return t({name:"decline",effect:"readonly",pinned:!0,description:"Call this when the question cannot be answered: nobody knows (unknowable), your records do not cover it (not_covered), it is not what you are for (out_of_scope), or answering would be unsafe. Use it instead of guessing. If the person could supply what is missing, ask them instead.",args:[{name:"because",type:"enum",required:!0,options:o},{name:"reason",type:"text",required:!0,maxLength:2e3,description:"One sentence the person who asked can read."}],run:({because:e,reason:n})=>({declined:!0,because:e,reason:n})})}export{i as decline,u as declinedFor,r as isDeclined};
@@ -0,0 +1 @@
1
+ export { type Declined, decline, declinedFor, isDeclined, type Unanswerable } from "./decline.js";
@@ -0,0 +1 @@
1
+ import{decline as i,declinedFor as c,isDeclined as l}from"./decline.js";export{i as decline,c as declinedFor,l as isDeclined};
@@ -1,3 +1,4 @@
1
+ import type { ToolResult } from "../../types/index.js";
1
2
  export interface EscalateOptions {
2
3
  /**
3
4
  * What actually happens — page someone, open a ticket, write to a queue.
@@ -19,6 +20,24 @@ export interface EscalationRequest {
19
20
  summary?: string;
20
21
  urgency: "low" | "normal" | "high";
21
22
  }
23
+ /** What `escalate` returns, so a run can be read back for whether a person was told. */
24
+ export interface Escalated {
25
+ escalated: true;
26
+ /** False when nothing was wired to receive it, or the handler was not reached. */
27
+ delivered: boolean;
28
+ request: EscalationRequest;
29
+ handled?: unknown;
30
+ }
31
+ /**
32
+ * True when this result is the model handing the work to a person.
33
+ *
34
+ * The counterpart to `isFinished`. A run that called `escalate` and a run that
35
+ * escalated are not the same thing — the call can fail, and telling a customer
36
+ * they have been passed on when nobody was told is the worse half of the two.
37
+ */
38
+ export declare function isEscalated(result: ToolResult): boolean;
39
+ /** Reads the request out of an escalated result, or null if it is not one. */
40
+ export declare function escalationOf(result: ToolResult): Escalated | null;
22
41
  /**
23
42
  * Hand back to a person rather than guess.
24
43
  *
@@ -1 +1 @@
1
- import{tool as i}from"../../tool/index.js";function u(e={}){return i({name:"escalate",description:"Hand this over to a person. Call it when you are missing something you cannot get, when the request needs an authority you do not have, or when acting on a guess would be worse than waiting. Say plainly why.",pinned:e.pinned??!0,args:[{name:"reason",type:"text",required:!0,maxLength:2e3},{name:"summary",type:"text",required:!1,maxLength:4e3},{name:"urgency",type:"enum",required:!1,options:["low","normal","high"]}],run:async({reason:a,summary:t,urgency:r="normal"})=>{const n={reason:a,summary:t,urgency:r},o=e.onEscalate?await e.onEscalate(n):void 0;return{escalated:!0,delivered:!!e.onEscalate,request:n,handled:o}}})}export{u as escalate};
1
+ import{tool as u}from"../../tool/index.js";function s(e){if(e.name!=="escalate"||e.status!=="completed")return!1;try{return JSON.parse(e.output).escalated===!0}catch{return!1}}function l(e){return s(e)?JSON.parse(e.output):null}function c(e={}){return u({name:"escalate",description:"Hand this over to a person. Call it when you are missing something you cannot get, when the request needs an authority you do not have, or when acting on a guess would be worse than waiting. Say plainly why.",pinned:e.pinned??!0,args:[{name:"reason",type:"text",required:!0,maxLength:2e3},{name:"summary",type:"text",required:!1,maxLength:4e3},{name:"urgency",type:"enum",required:!1,options:["low","normal","high"]}],run:async({reason:t,summary:a,urgency:r="normal"})=>{const n={reason:t,summary:a,urgency:r},o=e.onEscalate?await e.onEscalate(n):void 0;return{escalated:!0,delivered:!!e.onEscalate,request:n,handled:o}}})}export{c as escalate,l as escalationOf,s as isEscalated};
@@ -1 +1 @@
1
- export { type EscalateOptions, type EscalationRequest, escalate } from "./escalate.js";
1
+ export { type Escalated, type EscalateOptions, type EscalationRequest, escalate, escalationOf, isEscalated, } from "./escalate.js";
@@ -1 +1 @@
1
- import{escalate as o}from"./escalate.js";export{o as escalate};
1
+ import{escalate as s,escalationOf as t,isEscalated as c}from"./escalate.js";export{s as escalate,t as escalationOf,c as isEscalated};
@@ -0,0 +1,25 @@
1
+ export interface ExtractOptions {
2
+ /** Hosts a file may be read from. Default any public https host. */
3
+ allow?: string[];
4
+ /** Longest text handed back. Default 20000 characters. */
5
+ maxChars?: number;
6
+ /** Largest file read. Default 20 MB. */
7
+ maxBytes?: number;
8
+ /** Give up after this. Default 20000. */
9
+ timeoutMs?: number;
10
+ /** Injected for tests. */
11
+ fetch?: typeof fetch;
12
+ }
13
+ /**
14
+ * The text of a file at a URL — a PDF, a Word document, a page, or plain
15
+ * text — so a model can read what a link points at instead of guessing.
16
+ *
17
+ * The same guard as `http_request`: https only, no private addresses, and an
18
+ * allowlist you choose. Nothing is stored.
19
+ */
20
+ export declare function extract(opts?: ExtractOptions): import("../../index.js").Tool<import("../../index.js").ArgsOf<readonly [{
21
+ readonly name: "url";
22
+ readonly type: "string";
23
+ readonly required: true;
24
+ readonly maxLength: 2000;
25
+ }]>>;
@@ -0,0 +1 @@
1
+ import{tool as u}from"../../tool/index.js";import{ANY_HOST as w,checkUrl as y}from"../http/allow.js";import{followChecked as g}from"../http/hops.js";import{KINDS as x,kindOf as b,nameOf as l}from"./kinds.js";import{read as E}from"./readers.js";const T=2e4,_=20*1024*1024,p=2e4;function M(t={}){const o=t.allow?.length?t.allow:[w],m=t.fetch??globalThis.fetch.bind(globalThis),i=t.maxChars??T,s=t.maxBytes??_;return u({name:"extract",effect:"readonly",trust:"external",description:"Reads the file at a URL and returns its text: a PDF, a Word document (.docx), a web page, Markdown or plain text. Use it when a finding or a person hands you a link and you need what is actually on it, not a summary of it.",args:[{name:"url",type:"string",required:!0,maxLength:2e3}],run:async({url:d})=>{const e=y(d,o),h=AbortSignal.timeout(t.timeoutMs??p),r=await g(f=>m(f,{signal:h,redirect:"manual"}),e,o);if(!r.ok)throw new Error(`${e.hostname} answered ${r.status}.`);const c=b(r.headers.get("content-type"),e);if(!c)throw new Error(`Cannot read ${l(e)}: not one of ${x.join(", ")}, by type or by name.`);const n=await r.arrayBuffer();if(n.byteLength>s)throw new Error(`${l(e)} is ${n.byteLength} bytes, over the ${s} read.`);const a=await E(c,n);return a.length>i?`${a.slice(0,i).trimEnd()}\u2026`:a}})}export{M as extract};
@@ -0,0 +1,3 @@
1
+ export { type ExtractOptions, extract } from "./extract.js";
2
+ export { KINDS, type Kind, kindOf, nameOf } from "./kinds.js";
3
+ export { fromDocx, fromHtml, fromPdf, read } from "./readers.js";
@@ -0,0 +1 @@
1
+ import{extract as f}from"./extract.js";import{KINDS as e,kindOf as t,nameOf as x}from"./kinds.js";import{fromDocx as d,fromHtml as p,fromPdf as c,read as n}from"./readers.js";export{e as KINDS,f as extract,d as fromDocx,p as fromHtml,c as fromPdf,t as kindOf,x as nameOf,n as read};
@@ -0,0 +1,6 @@
1
+ export declare const KINDS: readonly ["pdf", "docx", "html", "md", "txt"];
2
+ export type Kind = (typeof KINDS)[number];
3
+ /** What a file is: its declared type first, the URL's extension when the type says nothing. */
4
+ export declare function kindOf(contentType: string | null, url: URL): Kind | null;
5
+ /** The name the file goes by, from the last path segment, or the host when there is none. */
6
+ export declare function nameOf(url: URL): string;
@@ -0,0 +1 @@
1
+ const a=["pdf","docx","html","md","txt"],p=[["application/pdf","pdf"],["application/vnd.openxmlformats-officedocument.wordprocessingml.document","docx"],["text/html","html"],["application/xhtml+xml","html"],["text/markdown","md"],["text/x-markdown","md"],["text/plain","txt"]],l={pdf:"pdf",docx:"docx",html:"html",htm:"html",md:"md",markdown:"md",txt:"txt",text:"txt"};function c(t,o){const e=(t??"").split(";")[0]?.trim().toLowerCase()??"",n=p.find(([d])=>d===e)?.[1];if(n)return n;const m=o.pathname.split(".").pop()?.toLowerCase()??"";return l[m]??null}function x(t){const o=t.pathname.split("/").filter(Boolean).pop();return o?decodeURIComponent(o):t.hostname}export{a as KINDS,c as kindOf,x as nameOf};
@@ -0,0 +1,7 @@
1
+ import type { Kind } from "./kinds.js";
2
+ /** The words in a page: scripts and styles gone, block ends become breaks, tags dropped. */
3
+ export declare function fromHtml(html: string): string;
4
+ /** The words in a Word document: each paragraph in `word/document.xml`, in order. */
5
+ export declare function fromDocx(bytes: ArrayBuffer): Promise<string>;
6
+ export declare function fromPdf(bytes: ArrayBuffer): Promise<string>;
7
+ export declare function read(kind: Kind, bytes: ArrayBuffer): Promise<string>;
@@ -0,0 +1,9 @@
1
+ import p from"jszip";import{extractText as m}from"unpdf";const u={amp:"&",lt:"<",gt:">",quot:'"',apos:"'",nbsp:" "};function o(t){return t.replace(/&(#\d+|#x[0-9a-f]+|[a-z]+);/gi,(r,e)=>e.startsWith("#x")?String.fromCodePoint(Number.parseInt(e.slice(2),16)):e.startsWith("#")?String.fromCodePoint(Number(e.slice(1))):u[e.toLowerCase()]??r)}function n(t){return t.replace(/[ \t]+\n/g,`
2
+ `).replace(/\n{3,}/g,`
3
+
4
+ `).trim()}function l(t){const r=t.replace(/<(script|style)\b[\s\S]*?<\/\1>/gi,"").replace(/<br\s*\/?>/gi,`
5
+ `).replace(/<\/(p|div|h[1-6]|li|tr|blockquote|pre|section|article|header|footer)>/gi,`
6
+
7
+ `).replace(/<[^>]+>/g,"");return n(o(r))}async function d(t){const e=(await p.loadAsync(t)).file("word/document.xml");if(!e)throw new Error("This is not a Word document: it has no word/document.xml.");const c=[...(await e.async("string")).matchAll(/<w:p\b[\s\S]*?<\/w:p>/g)].map(i=>[...i[0].matchAll(/<w:t\b[^>]*>([\s\S]*?)<\/w:t>/g)].map(s=>s[1]??"").join(""));return n(c.map(o).join(`
8
+
9
+ `))}async function f(t){const{text:r}=await m(new Uint8Array(t),{mergePages:!0});return n(r)}const a=new TextDecoder;async function h(t,r){switch(t){case"pdf":return f(r);case"docx":return d(r);case"html":return l(a.decode(r));default:return n(a.decode(r))}}export{d as fromDocx,l as fromHtml,f as fromPdf,h as read};
@@ -18,6 +18,10 @@ export declare function answerOf(result: ToolResult): string | null;
18
18
  *
19
19
  * Without it a loop ends by the model going quiet, which is indistinguishable
20
20
  * from it losing the thread.
21
+ *
22
+ * Pinned, so a shortlist cannot drop it. It is not a tool competing to be
23
+ * relevant to the question — it is how the loop is told to stop, and a turn
24
+ * offered four tools that are not this one has no way to say it is done.
21
25
  */
22
26
  export declare function finish(): import("../../index.js").Tool<import("../../index.js").ArgsOf<readonly [{
23
27
  readonly name: "answer";
@@ -1 +1 @@
1
- import{tool as n}from"../../tool/index.js";function t(e){if(e.name!=="finish"||e.status!=="completed")return!1;try{return JSON.parse(e.output).done===!0}catch{return!1}}function o(e){return t(e)?JSON.parse(e.output).answer:null}function a(){return n({name:"finish",effect:"readonly",description:"Call this when you have the answer and need no more tools. Pass the final answer as it should be given to the person who asked.",args:[{name:"answer",type:"text",required:!0,maxLength:2e4}],run:({answer:e})=>({done:!0,answer:e})})}export{o as answerOf,a as finish,t as isFinished};
1
+ import{tool as n}from"../../tool/index.js";function t(e){if(e.name!=="finish"||e.status!=="completed")return!1;try{return JSON.parse(e.output).done===!0}catch{return!1}}function o(e){return t(e)?JSON.parse(e.output).answer:null}function a(){return n({name:"finish",effect:"readonly",pinned:!0,description:"Call this when you have the answer and need no more tools. Pass the final answer as it should be given to the person who asked.",args:[{name:"answer",type:"text",required:!0,maxLength:2e4}],run:({answer:e})=>({done:!0,answer:e})})}export{o as answerOf,a as finish,t as isFinished};
@@ -1,3 +1,5 @@
1
+ /** Any public host at all; https and the private-address refusals still apply. */
2
+ export declare const ANY_HOST = "*";
1
3
  /**
2
4
  * The URL a request may go to, or the reason it may not.
3
5
  *
@@ -1 +1 @@
1
- const n=/^(10\.|127\.|0\.|169\.254\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.|100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\.)/,s=new Set(["localhost","metadata.google.internal"]);function i(r){const e=r.toLowerCase();return s.has(e)||e.endsWith(".local")||e.endsWith(".internal")||e.endsWith(".localhost")||e.startsWith("[")?!0:n.test(e)}function a(r,e){const t=r.toLowerCase(),o=e.toLowerCase();return o.startsWith(".")?t===o.slice(1)||t.endsWith(o):t===o}function h(r,e){let t;try{t=new URL(r)}catch{throw new Error(`"${r}" is not a URL.`)}if(t.protocol!=="https:")throw new Error(`Only https is allowed, and this is ${t.protocol.replace(":","")}.`);if(i(t.hostname))throw new Error(`${t.hostname} is a private or link-local address.`);if(!e.some(o=>a(t.hostname,o)))throw new Error(`${t.hostname} is not in the allowlist: ${e.join(", ")}.`);return t}export{h as checkUrl};
1
+ const n=/^(10\.|127\.|0\.|169\.254\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.|100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\.)/,s=new Set(["localhost","metadata.google.internal"]);function i(e){const t=e.toLowerCase();return s.has(t)||t.endsWith(".local")||t.endsWith(".internal")||t.endsWith(".localhost")||t.startsWith("[")?!0:n.test(t)}const a="*";function h(e,t){if(t===a)return!0;const r=e.toLowerCase(),o=t.toLowerCase();return o.startsWith(".")?r===o.slice(1)||r.endsWith(o):r===o}function c(e,t){let r;try{r=new URL(e)}catch{throw new Error(`"${e}" is not a URL.`)}if(r.protocol!=="https:")throw new Error(`Only https is allowed, and this is ${r.protocol.replace(":","")}.`);if(i(r.hostname))throw new Error(`${r.hostname} is a private or link-local address.`);if(!t.some(o=>h(r.hostname,o)))throw new Error(`${r.hostname} is not in the allowlist: ${t.join(", ")}.`);return r}export{a as ANY_HOST,c as checkUrl};
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The response, with every hop checked against the allowlist rather than only
3
+ * the URL the model named.
4
+ *
5
+ * Followed automatically, an allowlisted host answering `302 Location: …` sends
6
+ * the request anywhere it likes — a private address, a metadata endpoint — and
7
+ * carries any configured header with it. `send` must therefore pass
8
+ * `redirect: "manual"`.
9
+ */
10
+ export declare function followChecked(send: (to: URL) => Promise<Response>, target: URL, allow: string[]): Promise<Response>;
@@ -0,0 +1 @@
1
+ import{checkUrl as s}from"./allow.js";const a=3,c=e=>e.status>=300&&e.status<400;async function l(e,o,i){let t=await e(o);for(let r=0;r<a&&c(t);r++){const n=t.headers.get("location");if(!n)break;t=await e(s(new URL(n,t.url||o).toString(),i))}if(c(t))throw new Error(`Gave up after ${a} redirects from ${o.hostname}.`);return t}export{l as followChecked};
@@ -3,7 +3,8 @@ export interface HttpOptions {
3
3
  * Hosts this tool may reach. Required, and there is no default.
4
4
  *
5
5
  * `"api.example.com"` matches that host; `".example.com"` matches it and any
6
- * subdomain.
6
+ * subdomain; `"*"` matches every public host, and is the choice to make only
7
+ * when the tool is a reader of the open web rather than a client of your APIs.
7
8
  */
8
9
  allow: string[];
9
10
  /** Sent on every request — an API key belongs here, never in the args. */
@@ -14,6 +15,8 @@ export interface HttpOptions {
14
15
  maxChars?: number;
15
16
  /** Injected for tests. */
16
17
  fetch?: typeof fetch;
18
+ /** Injected for tests, for reading a `Retry-After` given as a date. */
19
+ now?: () => number;
17
20
  }
18
21
  /**
19
22
  * An HTTP GET or POST, to hosts you named.
@@ -24,6 +27,11 @@ export interface HttpOptions {
24
27
  *
25
28
  * Its `effect` is left unset on purpose: a GET is readonly and a POST to your
26
29
  * allowlist may be anything at all, and one label cannot be true of both.
30
+ *
31
+ * It makes one request and never retries. Retrying is the caller's, because
32
+ * only the caller knows whether the call is safe to repeat — and the one thing
33
+ * a caller needs to retry well, `retryAfterMs`, is handed back rather than
34
+ * discarded with the rest of the headers.
27
35
  */
28
36
  export declare function httpRequest(opts: HttpOptions): import("../../index.js").Tool<import("../../index.js").ArgsOf<readonly [{
29
37
  readonly name: "url";
@@ -1 +1 @@
1
- import{tool as w}from"../../tool/index.js";import{checkUrl as h}from"./allow.js";const p=1e4,T=1e5,u=3,d=t=>t.status>=300&&t.status<400;function _(t){if(!t.allow?.length)throw new Error("httpRequest needs an allow list of hosts; there is no safe default.");const m=t.fetch??globalThis.fetch.bind(globalThis),o=t.maxChars??T;return w({name:"http_request",description:`Fetches a URL and returns its status and body. Only these hosts are reachable: ${t.allow.join(", ")}. Use it to read an API or a page you have been pointed at.`,args:[{name:"url",type:"string",required:!0,maxLength:2e3},{name:"method",type:"enum",required:!1,options:["GET","POST"]},{name:"body",type:"text",required:!1,maxLength:2e4}],run:async({url:f,method:s="GET",body:n})=>{const r=h(f,t.allow),g=AbortSignal.timeout(t.timeoutMs??p),i=a=>m(a,{method:s,signal:g,redirect:"manual",headers:{...t.headers,...n?{"content-type":"application/json"}:{}},...n&&s==="POST"?{body:n}:{}});let e=await i(r);for(let a=0;a<u&&d(e);a++){const c=e.headers.get("location");if(!c)break;e=await i(h(new URL(c,e.url||r).toString(),t.allow))}if(d(e))throw new Error(`Gave up after ${u} redirects from ${r.hostname}.`);const l=await e.text();return{status:e.status,ok:e.ok,body:l.slice(0,o),truncated:l.length>o}}})}export{_ as httpRequest};
1
+ import{tool as d}from"../../tool/index.js";import{ANY_HOST as m,checkUrl as f}from"./allow.js";import{followChecked as g}from"./hops.js";import{retryAfterMs as p}from"./retry-after.js";const y=1e4,w=1e5;function T(e){return e.includes(m)?"Any public https host is reachable.":`Only these hosts are reachable: ${e.join(", ")}.`}function q(e){if(!e.allow?.length)throw new Error("httpRequest needs an allow list of hosts; there is no safe default.");const i=e.fetch??globalThis.fetch.bind(globalThis),a=e.maxChars??w;return d({name:"http_request",description:`Fetches a URL and returns its status and body. ${T(e.allow)} Use it to read an API or a page you have been pointed at.`,args:[{name:"url",type:"string",required:!0,maxLength:2e3},{name:"method",type:"enum",required:!1,options:["GET","POST"]},{name:"body",type:"text",required:!1,maxLength:2e4}],run:async({url:l,method:n="GET",body:r})=>{const c=f(l,e.allow),h=AbortSignal.timeout(e.timeoutMs??y),t=await g(u=>i(u,{method:n,signal:h,redirect:"manual",headers:{...e.headers,...r?{"content-type":"application/json"}:{}},...r&&n==="POST"?{body:r}:{}}),c,e.allow),s=await t.text(),o=p(t.headers.get("retry-after"),e.now);return{status:t.status,ok:t.ok,body:s.slice(0,a),truncated:s.length>a,...o===null?{}:{retryAfterMs:o}}}})}export{q as httpRequest};
@@ -1,2 +1,4 @@
1
1
  export { checkUrl } from "./allow.js";
2
+ export { followChecked } from "./hops.js";
2
3
  export { type HttpOptions, httpRequest } from "./http.js";
4
+ export { retryAfterMs } from "./retry-after.js";
@@ -1 +1 @@
1
- import{checkUrl as t}from"./allow.js";import{httpRequest as p}from"./http.js";export{t as checkUrl,p as httpRequest};
1
+ import{checkUrl as o}from"./allow.js";import{followChecked as f}from"./hops.js";import{httpRequest as m}from"./http.js";import{retryAfterMs as c}from"./retry-after.js";export{o as checkUrl,f as followChecked,m as httpRequest,c as retryAfterMs};
@@ -0,0 +1,14 @@
1
+ /**
2
+ * How long a server asked you to wait, in milliseconds, or null.
3
+ *
4
+ * `Retry-After` is the one part of a 429 or a 503 that is not guesswork: the
5
+ * server knows when its window resets and says so. Dropping it leaves a caller
6
+ * backing off by a schedule it invented, which is either too slow or — the
7
+ * expensive one — too fast, and a retry inside the window spends a request to
8
+ * be told to wait again.
9
+ *
10
+ * Two forms, both in the spec: whole seconds, or an HTTP date. A date in the
11
+ * past is zero rather than negative, because "wait -4 seconds" is not something
12
+ * a caller should have to guard against.
13
+ */
14
+ export declare function retryAfterMs(header: string | null, now?: () => number): number | null;
@@ -0,0 +1 @@
1
+ function u(r,n=Date.now){if(!r)return null;const t=r.trim();if(/^\d+$/.test(t))return Number(t)*1e3;const e=Date.parse(t);return Number.isNaN(e)?null:Math.max(0,e-n())}export{u as retryAfterMs};
@@ -1,12 +1,17 @@
1
1
  export { calculate, evaluate } from "./calculate/index.js";
2
2
  export { type ClockOptions, currentTime } from "./clock/index.js";
3
3
  export { type CodeExecOptions, codeExec, type Sandbox, type SandboxRequest, type SandboxResult, } from "./code-exec/index.js";
4
- export { dateMath } from "./date/index.js";
5
- export { type DeepRecallOptions, deepRecall, type RecalledFact, type Recaller, type RecallResult, } from "./deep-recall/index.js";
6
- export { type EscalateOptions, type EscalationRequest, escalate } from "./escalate/index.js";
4
+ export { dateMath, dates } from "./date/index.js";
5
+ export { type Declined, decline, declinedFor, isDeclined, type Unanswerable, } from "./decline/index.js";
6
+ export { type Escalated, type EscalateOptions, type EscalationRequest, escalate, escalationOf, isEscalated, } from "./escalate/index.js";
7
+ export { type ExtractOptions, extract } from "./extract/index.js";
7
8
  export { answerOf, type Finished, finish, isFinished } from "./finish/index.js";
8
- export { checkUrl, type HttpOptions, httpRequest } from "./http/index.js";
9
- export { type JsonQueryOptions, jsonQuery } from "./json/index.js";
9
+ export { checkUrl, type HttpOptions, httpRequest, retryAfterMs } from "./http/index.js";
10
+ export { type JsonQueryOptions, jsonFrom, jsonQuery } from "./json/index.js";
11
+ export { type NotesOptions, type NotesStore, notes, notesStore } from "./notes/index.js";
12
+ export { type OpenAlexApi, type OpenAlexOptions, openalex } from "./openalex/index.js";
13
+ export { type PaperSearchOptions, paperSearch } from "./paper-search/index.js";
14
+ export { type Publisher, type PublishOptions, publish } from "./publish/index.js";
10
15
  export { think } from "./think/index.js";
11
16
  export { type SearchDepth, type WebSearchOptions, webSearch } from "./web-search/index.js";
12
- export { type ArtifactFormat, CommissionAbortedError, type CommissionClient, CommissionTimeoutError, DEFAULT_DESIGN, type DesignGuide, mergeDesign, renderDesign, type WriteDocumentOptions, writeDocument, } from "./write-document/index.js";
17
+ export { type WikidataApi, type WikidataOptions, wikidata } from "./wikidata/index.js";
@@ -1 +1 @@
1
- import{calculate as o,evaluate as t}from"./calculate/index.js";import{currentTime as i}from"./clock/index.js";import{codeExec as p}from"./code-exec/index.js";import{dateMath as x}from"./date/index.js";import{deepRecall as a}from"./deep-recall/index.js";import{escalate as h}from"./escalate/index.js";import{answerOf as u,finish as d,isFinished as D}from"./finish/index.js";import{checkUrl as g,httpRequest as w}from"./http/index.js";import{jsonQuery as b}from"./json/index.js";import{think as A}from"./think/index.js";import{webSearch as F}from"./web-search/index.js";import{CommissionAbortedError as S,CommissionTimeoutError as U,DEFAULT_DESIGN as j,mergeDesign as q,renderDesign as v,writeDocument as y}from"./write-document/index.js";export{S as CommissionAbortedError,U as CommissionTimeoutError,j as DEFAULT_DESIGN,u as answerOf,o as calculate,g as checkUrl,p as codeExec,i as currentTime,x as dateMath,a as deepRecall,h as escalate,t as evaluate,d as finish,w as httpRequest,D as isFinished,b as jsonQuery,q as mergeDesign,v as renderDesign,A as think,F as webSearch,y as writeDocument};
1
+ import{calculate as o,evaluate as t}from"./calculate/index.js";import{currentTime as a}from"./clock/index.js";import{codeExec as x}from"./code-exec/index.js";import{dateMath as s,dates as c}from"./date/index.js";import{decline as n,declinedFor as l,isDeclined as d}from"./decline/index.js";import{escalate as u,escalationOf as k,isEscalated as w}from"./escalate/index.js";import{extract as S}from"./extract/index.js";import{answerOf as j,finish as y,isFinished as E}from"./finish/index.js";import{checkUrl as O,httpRequest as q,retryAfterMs as v}from"./http/index.js";import{jsonFrom as D,jsonQuery as Q}from"./json/index.js";import{notes as T,notesStore as U}from"./notes/index.js";import{openalex as z}from"./openalex/index.js";import{paperSearch as C}from"./paper-search/index.js";import{publish as H}from"./publish/index.js";import{think as J}from"./think/index.js";import{webSearch as L}from"./web-search/index.js";import{wikidata as P}from"./wikidata/index.js";export{j as answerOf,o as calculate,O as checkUrl,x as codeExec,a as currentTime,s as dateMath,c as dates,n as decline,l as declinedFor,u as escalate,k as escalationOf,t as evaluate,S as extract,y as finish,q as httpRequest,d as isDeclined,w as isEscalated,E as isFinished,D as jsonFrom,Q as jsonQuery,T as notes,U as notesStore,z as openalex,C as paperSearch,H as publish,v as retryAfterMs,J as think,L as webSearch,P as wikidata};
@@ -1,2 +1,3 @@
1
1
  export { type JsonQueryOptions, jsonQuery } from "./json.js";
2
+ export { jsonFrom } from "./parse.js";
2
3
  export { readPath } from "./path.js";
@@ -1 +1 @@
1
- import{jsonQuery as e}from"./json.js";import{readPath as a}from"./path.js";export{e as jsonQuery,a as readPath};
1
+ import{jsonQuery as e}from"./json.js";import{jsonFrom as t}from"./parse.js";import{readPath as p}from"./path.js";export{t as jsonFrom,e as jsonQuery,p as readPath};
@@ -1 +1 @@
1
- import{tool as i}from"../../tool/index.js";import{readPath as l}from"./path.js";const d=2e4;function f(a={}){const e=a.maxChars??d;return i({name:"json_query",effect:"readonly",description:"Reads one value out of a JSON document by path. Paths look like `invoices[0].total`, or `invoices[*].total` for every element. Use it instead of reading a long document yourself.",args:[{name:"json",type:"text",required:!0,maxLength:2e5},{name:"path",type:"string",required:!0,maxLength:500}],run:({json:s,path:t})=>{let o;try{o=JSON.parse(s)}catch(u){throw new Error(`json is not valid JSON: ${u.message}`)}const n=l(o,t);if(n===void 0)return{found:!1,path:t,value:null};const r=JSON.stringify(n)??"null";return{found:!0,path:t,value:n,truncated:r.length>e,...r.length>e?{value:`${r.slice(0,e)}\u2026`}:{}}}})}export{f as jsonQuery};
1
+ import{tool as s}from"../../tool/index.js";import{jsonFrom as i}from"./parse.js";import{readPath as l}from"./path.js";const d=2e4;function g(r={}){const e=r.maxChars??d;return s({name:"json_query",effect:"readonly",description:"Reads one value out of a JSON document by path. Paths look like `invoices[0].total`, or `invoices[*].total` for every element. Use it instead of reading a long document yourself.",args:[{name:"json",type:"text",required:!0,maxLength:2e5},{name:"path",type:"string",required:!0,maxLength:500}],run:({json:a,path:n})=>{const u=i(a),t=l(u,n);if(t===void 0)return{found:!1,path:n,value:null};const o=JSON.stringify(t)??"null";return{found:!0,path:n,value:t,truncated:o.length>e,...o.length>e?{value:`${o.slice(0,e)}\u2026`}:{}}}})}export{g as jsonQuery};
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The JSON inside a model's reply, whatever it wrapped it in.
3
+ *
4
+ * The two commonest structured-output failures are not bad JSON at all: a
5
+ * fenced block, and a sentence before or after it. Both parse perfectly once
6
+ * the payload is found, and every caller otherwise writes the same fence
7
+ * stripping by hand and gets the second case wrong.
8
+ *
9
+ * It finds the JSON; it never rewrites it. A trailing comma, a single-quoted
10
+ * key or a missing brace stays an error, because repairing those is guessing at
11
+ * what was meant and a wrong guess is a confidently wrong record. Those belong
12
+ * in a bounded repair round, where the model is told what failed and answers
13
+ * again.
14
+ */
15
+ export declare function jsonFrom(text: string): unknown;
@@ -0,0 +1 @@
1
+ const l=/```(?:[a-zA-Z0-9_-]+)?\s*\n?([\s\S]*?)```/,f={"{":"}","[":"]"};function a(e){const n=e.search(/[[{]/);if(n===-1)return null;const t=f[e[n]];let o=0,i=!1,s=!1;for(let r=n;r<e.length;r+=1){const c=e[r];if(s){s=!1;continue}if(c==="\\"&&i){s=!0;continue}if(c==='"'){i=!i;continue}if(!i){if(c===e[n])o+=1;else if(c===t&&(o-=1,o===0))return e.slice(n,r+1)}}return null}function u(e){const n=e.trim();for(const t of[n,l.exec(n)?.[1]?.trim(),a(n)])if(t)try{return JSON.parse(t)}catch{}throw new Error(`No JSON in ${n.length>80?`${n.slice(0,80)}\u2026`:n||"an empty reply"}`)}export{u as jsonFrom};
@@ -0,0 +1 @@
1
+ export { ACTIONS, type NotesOptions, type NotesStore, notes, notesStore } from "./notes.js";
@@ -0,0 +1 @@
1
+ import{ACTIONS as t,notes as r,notesStore as n}from"./notes.js";export{t as ACTIONS,r as notes,n as notesStore};
@@ -0,0 +1,45 @@
1
+ /** Boards by session, one per process unless you hand every bus the same one. */
2
+ export type NotesStore = Map<string, string[]>;
3
+ export interface NotesOptions {
4
+ /** Whose board: a session id, so a follow-up question sees what the last run worked out. */
5
+ session: string;
6
+ /** Where boards live. Default one store for the process; pass your own to share or to inspect. */
7
+ store?: NotesStore;
8
+ /** Notes a board holds before `post` is refused. Default 50. */
9
+ maxNotes?: number;
10
+ }
11
+ export declare const ACTIONS: readonly ["post", "read", "replace", "clear"];
12
+ export declare function notesStore(): NotesStore;
13
+ /**
14
+ * A board the model writes to and reads back during a session — what it is
15
+ * doing, what is still missing, what it decided — kept in memory and gone when
16
+ * the process is.
17
+ *
18
+ * `think` is write-once and never seen again; this is the model's own chosen
19
+ * state, readable three steps later. It is not memory: a note worth keeping
20
+ * goes to `remember`, on purpose, so half-thoughts never surface as facts.
21
+ *
22
+ * `replace` is what makes it usable as a running plan. Append-only, a plan
23
+ * re-posted each turn becomes twenty notes where the twentieth contradicts the
24
+ * third and `read` shows both — and the only way back is `clear`, which throws
25
+ * the plan away with the noise. One note, rewritten, is the whole pattern.
26
+ *
27
+ * No effect label: `read` changes nothing and `post` changes only this board.
28
+ */
29
+ export declare function notes(opts: NotesOptions): import("../../index.js").Tool<import("../../index.js").ArgsOf<readonly [{
30
+ readonly name: "action";
31
+ readonly type: "enum";
32
+ readonly required: true;
33
+ readonly options: readonly ["post", "read", "replace", "clear"];
34
+ }, {
35
+ readonly name: "note";
36
+ readonly type: "text";
37
+ readonly required: false;
38
+ readonly maxLength: 2000;
39
+ }, {
40
+ readonly name: "number";
41
+ readonly type: "number";
42
+ readonly required: false;
43
+ readonly min: 1;
44
+ readonly description: "Which note to replace, as `read` numbers them.";
45
+ }]>>;
@@ -0,0 +1,2 @@
1
+ import{tool as d}from"../../tool/index.js";const l=["post","read","replace","clear"],u=50,c=2e3,w=new Map;function p(){return new Map}function m(e){return e.map((t,n)=>`#${n+1} ${t}`).join(`
2
+ `)}function f(e,t,n){if(t===void 0)throw new Error("replace needs the `number` of the note.");if(e.length===0)throw new Error("There is nothing on the board to replace.");if(t<1||t>e.length)throw new Error(`There is no note #${t}; the board holds #1 to #${e.length}.`);const o=[...e];return o[t-1]=n,o}function y(e){if(!e.session?.trim())throw new Error("notes needs the session whose board this is.");const t=e.store??w,n=e.maxNotes??u,o=()=>t.get(e.session)??[];return d({name:"notes",description:"Your notes for this session. `post` writes one \u2014 what you are doing, what is still missing, what you decided. `read` shows them all, numbered, in order. `replace` rewrites one by its number \u2014 keep a plan as a single note and rewrite it as you go, rather than posting its status again each turn. `clear` wipes them. They last the session and nobody else reads them; use `remember` for anything that should outlast it.",args:[{name:"action",type:"enum",required:!0,options:[...l]},{name:"note",type:"text",required:!1,maxLength:c},{name:"number",type:"number",required:!1,min:1,description:"Which note to replace, as `read` numbers them."}],run:({action:s,note:h,number:a})=>{const r=o();if(s==="read")return r.length?m(r):"Nothing on the board yet.";if(s==="clear")return t.delete(e.session),`Cleared ${r.length} note${r.length===1?"":"s"}.`;const i=h?.trim();if(!i)throw new Error(`${s} needs a \`note\`.`);if(s==="replace")return t.set(e.session,f(r,a,i)),`#${a} rewritten.`;if(r.length>=n)throw new Error(`The board holds ${n} notes; clear it, or say less.`);return t.set(e.session,[...r,i]),`#${r.length+1} noted.`}})}export{l as ACTIONS,y as notes,p as notesStore};