@spendgraph/tools 0.4.0 → 0.6.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 (112) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +25 -258
  3. package/dist/bogus/ask-mum.d.ts +5 -0
  4. package/dist/bogus/ask-mum.js +1 -0
  5. package/dist/bogus/book-taxi.d.ts +9 -0
  6. package/dist/bogus/book-taxi.js +1 -0
  7. package/dist/bogus/bus.d.ts +8 -0
  8. package/dist/bogus/bus.js +1 -0
  9. package/dist/bogus/calculator.d.ts +14 -0
  10. package/dist/bogus/calculator.js +1 -0
  11. package/dist/bogus/dictionary.d.ts +6 -0
  12. package/dist/bogus/dictionary.js +1 -0
  13. package/dist/bogus/feed-cat.d.ts +8 -0
  14. package/dist/bogus/feed-cat.js +1 -0
  15. package/dist/bogus/football-score.d.ts +6 -0
  16. package/dist/bogus/football-score.js +1 -0
  17. package/dist/bogus/gold-price.d.ts +7 -0
  18. package/dist/bogus/gold-price.js +1 -0
  19. package/dist/bogus/index.d.ts +14 -0
  20. package/dist/bogus/index.js +1 -0
  21. package/dist/bogus/my-calendar.d.ts +6 -0
  22. package/dist/bogus/my-calendar.js +1 -0
  23. package/dist/bogus/my-notes.d.ts +6 -0
  24. package/dist/bogus/my-notes.js +1 -0
  25. package/dist/bogus/my-recipes.d.ts +6 -0
  26. package/dist/bogus/my-recipes.js +1 -0
  27. package/dist/bogus/order-pizza.d.ts +12 -0
  28. package/dist/bogus/order-pizza.js +1 -0
  29. package/dist/bogus/prayer-times.d.ts +6 -0
  30. package/dist/bogus/prayer-times.js +1 -0
  31. package/dist/bogus/receipt.d.ts +8 -0
  32. package/dist/bogus/receipt.js +1 -0
  33. package/dist/builtin/calculate/calculate.js +1 -21
  34. package/dist/builtin/calculate/exact.d.ts +16 -0
  35. package/dist/builtin/calculate/exact.js +1 -0
  36. package/dist/builtin/calculate/index.js +1 -2
  37. package/dist/builtin/calculate/parse.js +1 -137
  38. package/dist/builtin/clock/clock.js +1 -61
  39. package/dist/builtin/clock/index.js +1 -1
  40. package/dist/builtin/code-exec/code-exec.d.ts +28 -0
  41. package/dist/builtin/code-exec/code-exec.js +1 -0
  42. package/dist/builtin/code-exec/index.d.ts +1 -0
  43. package/dist/builtin/code-exec/index.js +1 -0
  44. package/dist/builtin/date/date.js +1 -41
  45. package/dist/builtin/date/index.js +1 -2
  46. package/dist/builtin/date/shift.js +1 -47
  47. package/dist/builtin/deep-recall/deep-recall.d.ts +39 -2
  48. package/dist/builtin/deep-recall/deep-recall.js +1 -45
  49. package/dist/builtin/deep-recall/index.d.ts +1 -1
  50. package/dist/builtin/deep-recall/index.js +1 -1
  51. package/dist/builtin/escalate/escalate.js +1 -20
  52. package/dist/builtin/escalate/index.js +1 -1
  53. package/dist/builtin/finish/finish.js +1 -26
  54. package/dist/builtin/finish/index.js +1 -1
  55. package/dist/builtin/http/allow.js +1 -37
  56. package/dist/builtin/http/http.js +1 -41
  57. package/dist/builtin/http/index.js +1 -2
  58. package/dist/builtin/index.d.ts +2 -1
  59. package/dist/builtin/index.js +1 -11
  60. package/dist/builtin/json/index.js +1 -2
  61. package/dist/builtin/json/json.js +1 -37
  62. package/dist/builtin/json/path.js +1 -56
  63. package/dist/builtin/think/index.js +1 -1
  64. package/dist/builtin/think/think.js +1 -12
  65. package/dist/builtin/web-search/index.js +1 -1
  66. package/dist/builtin/web-search/reply.js +1 -27
  67. package/dist/builtin/web-search/web-search.d.ts +6 -1
  68. package/dist/builtin/web-search/web-search.js +1 -103
  69. package/dist/builtin/write-document/commission.js +13 -152
  70. package/dist/builtin/write-document/design.js +5 -86
  71. package/dist/builtin/write-document/http.js +1 -71
  72. package/dist/builtin/write-document/index.js +1 -4
  73. package/dist/builtin/write-document/types.js +0 -1
  74. package/dist/builtin/write-document/write-document.js +1 -142
  75. package/dist/bus/args.d.ts +11 -0
  76. package/dist/bus/args.js +1 -0
  77. package/dist/bus/bus.js +1 -49
  78. package/dist/bus/group.d.ts +8 -0
  79. package/dist/bus/group.js +1 -0
  80. package/dist/bus/index.d.ts +1 -0
  81. package/dist/bus/index.js +1 -2
  82. package/dist/bus/invoke.js +1 -43
  83. package/dist/bus/options.js +0 -1
  84. package/dist/bus/output.d.ts +26 -0
  85. package/dist/bus/output.js +2 -0
  86. package/dist/bus/overlap.js +1 -31
  87. package/dist/bus/score.d.ts +11 -9
  88. package/dist/bus/score.js +1 -12
  89. package/dist/bus/select.d.ts +2 -1
  90. package/dist/bus/select.js +1 -44
  91. package/dist/bus/step.js +1 -11
  92. package/dist/bus/terms.d.ts +25 -0
  93. package/dist/bus/terms.js +1 -0
  94. package/dist/bus/trace.js +1 -22
  95. package/dist/index.d.ts +13 -3
  96. package/dist/index.js +1 -3
  97. package/dist/internals.d.ts +1 -1
  98. package/dist/internals.js +1 -10
  99. package/dist/tool/bind.js +1 -64
  100. package/dist/tool/index.js +1 -2
  101. package/dist/tool/tool.d.ts +1 -0
  102. package/dist/tool/tool.js +1 -51
  103. package/dist/types/index.js +0 -1
  104. package/dist/types/result.js +0 -1
  105. package/dist/types/tool.d.ts +13 -0
  106. package/dist/types/tool.js +0 -1
  107. package/dist/wire/index.js +1 -3
  108. package/dist/wire/json-schema.js +1 -65
  109. package/dist/wire/markdown.d.ts +5 -3
  110. package/dist/wire/markdown.js +9 -29
  111. package/dist/wire/providers.js +1 -18
  112. package/package.json +11 -7
@@ -0,0 +1 @@
1
+ import{tool as n}from"../tool/index.js";import{receipt as d}from"./receipt.js";const a={today:["09:30 standup","13:00 lunch with Rafi","19:00 futsal"],tomorrow:["11:00 dentist \u2014 the one you already moved twice"],saturday:[]};function y(){return n({name:"my_calendar",effect:"readonly",description:"What is on the user's calendar for a given day.",args:[{name:"question",type:"string",required:!1,description:"Names a day \u2014 today, tomorrow, or saturday. Defaults to today."}],run:({question:o="today"})=>{const e=Object.keys(a).find(r=>o.toLowerCase().includes(r))??"today",t=a[e]??[];return{day:e,events:t,free:t.length===0,receipt:d("my_calendar",e)}}})}export{y as myCalendar};
@@ -0,0 +1,6 @@
1
+ export declare function myNotes(): import("../index.js").Tool<import("../index.js").ArgsOf<readonly [{
2
+ readonly name: "question";
3
+ readonly type: "string";
4
+ readonly required: true;
5
+ readonly description: "What to look for.";
6
+ }]>>;
@@ -0,0 +1 @@
1
+ import{tool as s}from"../tool/index.js";import{receipt as r}from"./receipt.js";const t={workout:"PPL, 4 days. Push Mon, Pull Wed, Legs Fri, Rest Sun.",wifi:"Router password is on the fridge. Nobody knows the admin one.",passport:"Renewal needs the old passport, NID, and a 5,000 BDT fee.",landlord:"Rent is due on the 5th. He does not accept bKash. He will insist he does."};function d(){return s({name:"my_notes",effect:"readonly",description:"Search the user's own notes. Only place their personal facts live \u2014 passwords, plans, things they wrote down so they would not have to remember them.",args:[{name:"question",type:"string",required:!0,description:"What to look for."}],run:({question:o})=>{const e=Object.keys(t).find(n=>o.toLowerCase().includes(n));return e?{found:!0,note:t[e],written:"2026-08-01",receipt:r("my_notes",e)}:{found:!1,note:null,searched:o}}})}export{d as myNotes};
@@ -0,0 +1,6 @@
1
+ export declare function myRecipes(): import("../index.js").Tool<import("../index.js").ArgsOf<readonly [{
2
+ readonly name: "question";
3
+ readonly type: "string";
4
+ readonly required: true;
5
+ readonly description: "Names a dish.";
6
+ }]>>;
@@ -0,0 +1 @@
1
+ import{tool as i}from"../tool/index.js";import{receipt as s}from"./receipt.js";const o={khichuri:"Rice, moong dal, ginger, whole spices. 1:1 rice to dal. Do not stir it to death.",omelette:"Three eggs, one onion, one green chilli. Butter, not oil. 90 seconds a side.",tea:"Boil water. Add tea. Add milk. Argue about the order for twenty minutes."};function a(){return i({name:"my_recipes",effect:"readonly",description:"The user's saved recipes. Not a cookbook \u2014 only what they saved themselves.",args:[{name:"question",type:"string",required:!0,description:"Names a dish."}],run:({question:t})=>{const e=Object.keys(o).find(r=>t.toLowerCase().includes(r));return e?{found:!0,dish:e,method:o[e],receipt:s("my_recipes",e)}:{found:!1,dish:null,asked:t}}})}export{a as myRecipes};
@@ -0,0 +1,12 @@
1
+ export declare function orderPizza(): import("../index.js").Tool<import("../index.js").ArgsOf<readonly [{
2
+ readonly name: "pizza";
3
+ readonly type: "enum";
4
+ readonly required: true;
5
+ readonly options: string[];
6
+ }, {
7
+ readonly name: "quantity";
8
+ readonly type: "number";
9
+ readonly required: false;
10
+ readonly min: 1;
11
+ readonly max: 10;
12
+ }]>>;
@@ -0,0 +1 @@
1
+ import{tool as a}from"../tool/index.js";import{receipt as o}from"./receipt.js";const i={margherita:690,pepperoni:890,"beef bulgogi":1090};function d(){return a({name:"order_pizza",effect:"destructive",description:"Places a real pizza order and charges the card on file. There is no undo. A pizza will arrive whether or not it was wanted.",args:[{name:"pizza",type:"enum",required:!0,options:Object.keys(i)},{name:"quantity",type:"number",required:!1,min:1,max:10}],run:({pizza:e,quantity:r=1})=>({ordered:e,quantity:r,bdt:(i[e]??0)*r,arriving:"in 35 minutes",cancellable:!1,receipt:o("order_pizza",`${e}x${r}`)})})}export{d as orderPizza};
@@ -0,0 +1,6 @@
1
+ export declare function prayerTimes(): import("../index.js").Tool<import("../index.js").ArgsOf<readonly [{
2
+ readonly name: "question";
3
+ readonly type: "string";
4
+ readonly required: true;
5
+ readonly description: "Names a city.";
6
+ }]>>;
@@ -0,0 +1 @@
1
+ import{tool as i}from"../tool/index.js";import{receipt as o}from"./receipt.js";const t={dhaka:{fajr:"04:07",dhuhr:"12:41",asr:"16:03",maghrib:"18:29",isha:"19:53"},chittagong:{fajr:"04:01",dhuhr:"12:37",asr:"15:59",maghrib:"18:23",isha:"19:47"}};function m(){return i({name:"prayer_times",effect:"readonly",description:"Today's times from the Bogus Almanac, for Dhaka or Chittagong. They differ from any published table, so answer from what this returns rather than from memory.",args:[{name:"question",type:"string",required:!0,description:"Names a city."}],run:({question:r})=>{const e=Object.keys(t).find(a=>r.toLowerCase().includes(a));return e?{city:e,date:"2026-09-04",times:t[e],receipt:o("prayer_times",e)}:{asked:r,date:"2026-09-04",times:null,note:"city not covered"}}})}export{m as prayerTimes};
@@ -0,0 +1,8 @@
1
+ /**
2
+ * A token no model could produce without calling the tool.
3
+ *
4
+ * Every bogus reply carries one. Grep an answer for `bogus-` and you know which
5
+ * tools actually reached it — the model cannot invent a receipt it never saw,
6
+ * so provenance is observed rather than claimed.
7
+ */
8
+ export declare function receipt(tool: string, key: string): string;
@@ -0,0 +1 @@
1
+ function o(r,e){let t=0;for(const c of`${r}:${e}`)t=t*31+c.charCodeAt(0)>>>0;return`bogus-${r}-${t.toString(16).padStart(8,"0").slice(0,6)}`}export{o as receipt};
@@ -1,21 +1 @@
1
- import { tool } from "../../tool/index.js";
2
- import { evaluate } from "./parse.js";
3
- export function calculate() {
4
- return tool({
5
- name: "calculate",
6
- effect: "readonly",
7
- description: "Works out an arithmetic expression exactly. Use it for any sum that matters — " +
8
- "totals, percentages, differences, rates. Supports + - * / % ^, brackets, and " +
9
- "sqrt, abs, round, floor, ceil, ln, log.",
10
- args: [
11
- {
12
- name: "expression",
13
- type: "string",
14
- required: true,
15
- maxLength: 500,
16
- description: "For example (1250 * 0.2) + 99, or sqrt(144).",
17
- },
18
- ],
19
- run: ({ expression }) => evaluate(expression),
20
- });
21
- }
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};
@@ -0,0 +1,16 @@
1
+ /**
2
+ * A result as the number that was worked out, not as the binary that holds it.
3
+ *
4
+ * The tool's own description promises to work an expression out *exactly*, and
5
+ * `JSON.stringify(0.1 + 0.2)` is `"0.30000000000000004"`. That number goes to
6
+ * the model, and the model says it to a person — about money, in a repo whose
7
+ * first rule is that costs are integers because floats do not sum exactly.
8
+ *
9
+ * Rounding to the fifteen significant digits a double genuinely carries removes
10
+ * the artefact and keeps every digit that was real: `0.1 + 0.2` is `0.3`,
11
+ * `(1.1 - 1) * 10` is `1`, and `1 / 3` is still `0.333333333333333`.
12
+ *
13
+ * This is presentation, not arithmetic. It cannot make a float exact; it stops
14
+ * the tool claiming a precision the double never had.
15
+ */
16
+ export declare function exact(value: number): string;
@@ -0,0 +1 @@
1
+ const t=15;function n(r){return Number.isInteger(r)?String(r):String(Number.parseFloat(r.toPrecision(15)))}export{n as exact};
@@ -1,2 +1 @@
1
- export { calculate } from "./calculate.js";
2
- export { evaluate } from "./parse.js";
1
+ import{calculate as o}from"./calculate.js";import{evaluate as t}from"./parse.js";export{o as calculate,t as evaluate};
@@ -1,137 +1 @@
1
- const FUNCTIONS = {
2
- sqrt: Math.sqrt,
3
- abs: Math.abs,
4
- round: Math.round,
5
- floor: Math.floor,
6
- ceil: Math.ceil,
7
- ln: Math.log,
8
- log: Math.log10,
9
- };
10
- const OPERATORS = new Set(["+", "-", "*", "/", "%", "^", "(", ")", ","]);
11
- function tokenize(input) {
12
- const tokens = [];
13
- let i = 0;
14
- while (i < input.length) {
15
- const ch = input[i];
16
- if (ch === " " || ch === "\t" || ch === "_") {
17
- i++;
18
- continue;
19
- }
20
- if (ch >= "0" && ch <= "9") {
21
- let j = i;
22
- while (j < input.length && /[0-9._]/.test(input[j]))
23
- j++;
24
- const value = Number(input.slice(i, j).replace(/_/g, ""));
25
- if (!Number.isFinite(value))
26
- throw new Error(`"${input.slice(i, j)}" is not a number.`);
27
- tokens.push({ kind: "number", value });
28
- i = j;
29
- continue;
30
- }
31
- if (/[a-z]/i.test(ch)) {
32
- let j = i;
33
- while (j < input.length && /[a-z]/i.test(input[j]))
34
- j++;
35
- tokens.push({ kind: "op", value: input.slice(i, j).toLowerCase() });
36
- i = j;
37
- continue;
38
- }
39
- if (OPERATORS.has(ch)) {
40
- tokens.push({ kind: "op", value: ch });
41
- i++;
42
- continue;
43
- }
44
- throw new Error(`"${ch}" is not something this calculator understands.`);
45
- }
46
- return tokens;
47
- }
48
- class Reader {
49
- tokens;
50
- at = 0;
51
- constructor(tokens) {
52
- this.tokens = tokens;
53
- }
54
- peek() {
55
- return this.tokens[this.at];
56
- }
57
- take() {
58
- return this.tokens[this.at++];
59
- }
60
- eat(value) {
61
- const next = this.peek();
62
- if (next?.kind === "op" && next.value === value) {
63
- this.at++;
64
- return true;
65
- }
66
- return false;
67
- }
68
- get done() {
69
- return this.at >= this.tokens.length;
70
- }
71
- }
72
- function primary(r) {
73
- const token = r.take();
74
- if (!token)
75
- throw new Error("The expression ends where a number was expected.");
76
- if (token.kind === "number")
77
- return token.value;
78
- if (token.value === "-")
79
- return -primary(r);
80
- if (token.value === "+")
81
- return primary(r);
82
- if (token.value === "(") {
83
- const value = additive(r);
84
- if (!r.eat(")"))
85
- throw new Error("A bracket was opened and never closed.");
86
- return value;
87
- }
88
- const fn = FUNCTIONS[token.value];
89
- if (!fn)
90
- throw new Error(`"${token.value}" is not a function this calculator has.`);
91
- if (!r.eat("("))
92
- throw new Error(`${token.value} needs brackets, as in ${token.value}(9).`);
93
- const arg = additive(r);
94
- if (!r.eat(")"))
95
- throw new Error("A bracket was opened and never closed.");
96
- return fn(arg);
97
- }
98
- function power(r) {
99
- const base = primary(r);
100
- if (r.eat("^"))
101
- return base ** power(r);
102
- return base;
103
- }
104
- function multiplicative(r) {
105
- let value = power(r);
106
- for (;;) {
107
- if (r.eat("*"))
108
- value *= power(r);
109
- else if (r.eat("/"))
110
- value /= power(r);
111
- else if (r.eat("%"))
112
- value %= power(r);
113
- else
114
- return value;
115
- }
116
- }
117
- function additive(r) {
118
- let value = multiplicative(r);
119
- for (;;) {
120
- if (r.eat("+"))
121
- value += multiplicative(r);
122
- else if (r.eat("-"))
123
- value -= multiplicative(r);
124
- else
125
- return value;
126
- }
127
- }
128
- export function evaluate(expression) {
129
- const reader = new Reader(tokenize(expression));
130
- const value = additive(reader);
131
- if (!reader.done)
132
- throw new Error("There is something left over after the expression.");
133
- if (!Number.isFinite(value)) {
134
- throw new Error(value === Infinity || value === -Infinity ? "That divides by zero." : "That is not a number.");
135
- }
136
- return value;
137
- }
1
+ const l={sqrt:Math.sqrt,abs:Math.abs,round:Math.round,floor:Math.floor,ceil:Math.ceil,ln:Math.log,log:Math.log10},h=new Set(["+","-","*","/","%","^","(",")",","]);function c(e){const t=Number(e.replace(/_/g,""));if(!Number.isFinite(t))throw new Error(`"${e}" is not a number.`);if(!e.includes(".")&&!Number.isSafeInteger(t))throw new Error(`${e} is too large for this calculator to hold exactly. Whole numbers up to 9007199254740991 are safe.`);return t}function f(e){const t=[];let n=0;for(;n<e.length;){const o=e[n];if(o===" "||o===" "||o==="_"){n++;continue}if(o>="0"&&o<="9"){let r=n;for(;r<e.length&&/[0-9._]/.test(e[r]);)r++;t.push({kind:"number",value:c(e.slice(n,r))}),n=r;continue}if(/[a-z]/i.test(o)){let r=n;for(;r<e.length&&/[a-z]/i.test(e[r]);)r++;t.push({kind:"op",value:e.slice(n,r).toLowerCase()}),n=r;continue}if(h.has(o)){t.push({kind:"op",value:o}),n++;continue}throw new Error(`"${o}" is not something this calculator understands.`)}return t}class w{tokens;at=0;constructor(t){this.tokens=t}peek(){return this.tokens[this.at]}take(){return this.tokens[this.at++]}eat(t){const n=this.peek();return n?.kind==="op"&&n.value===t?(this.at++,!0):!1}get done(){return this.at>=this.tokens.length}}function s(e){const t=e.take();if(!t)throw new Error("The expression ends where a number was expected.");if(t.kind==="number")return t.value;if(t.value==="-")return-s(e);if(t.value==="+")return s(e);if(t.value==="("){const r=u(e);if(!e.eat(")"))throw new Error("A bracket was opened and never closed.");return r}const n=l[t.value];if(!n)throw new Error(`"${t.value}" is not a function this calculator has.`);if(!e.eat("("))throw new Error(`${t.value} needs brackets, as in ${t.value}(9).`);const o=u(e);if(!e.eat(")"))throw new Error("A bracket was opened and never closed.");return n(o)}function a(e){const t=s(e);return e.eat("^")?t**a(e):t}function i(e){let t=a(e);for(;;)if(e.eat("*"))t*=a(e);else if(e.eat("/"))t/=a(e);else if(e.eat("%"))t%=a(e);else return t}function u(e){let t=i(e);for(;;)if(e.eat("+"))t+=i(e);else if(e.eat("-"))t-=i(e);else return t}function d(e){const t=new w(f(e)),n=u(t);if(!t.done)throw new Error("There is something left over after the expression.");if(!Number.isFinite(n))throw new Error(n===1/0||n===-1/0?"That divides by zero.":"That is not a number.");return n}export{d as evaluate};
@@ -1,61 +1 @@
1
- import { tool } from "../../tool/index.js";
2
- export function currentTime(opts = {}) {
3
- const now = opts.now ?? (() => new Date());
4
- return tool({
5
- name: "current_time",
6
- effect: "readonly",
7
- description: "The current date and time. Call this before answering anything that " +
8
- "depends on today's date — ages, deadlines, 'how long ago', 'this year'. " +
9
- "Do not rely on your own sense of the date; it is the date you were " +
10
- "trained, not today.",
11
- args: [
12
- {
13
- name: "timezone",
14
- type: "string",
15
- required: false,
16
- description: "IANA zone such as Europe/London or Asia/Kolkata. Defaults to the server's zone.",
17
- },
18
- {
19
- name: "format",
20
- type: "enum",
21
- required: false,
22
- options: ["iso", "human", "date", "time"],
23
- description: "iso is unambiguous and the right default for anything machine-read.",
24
- },
25
- ],
26
- run: ({ timezone, format = "iso" }) => {
27
- const at = now();
28
- const zone = timezone ?? opts.defaultTimeZone ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
29
- try {
30
- new Intl.DateTimeFormat("en-GB", { timeZone: zone });
31
- }
32
- catch {
33
- throw new Error(`"${zone}" is not a known IANA time zone.`);
34
- }
35
- const answerZone = format === "iso" ? "UTC" : zone;
36
- const parts = (options) => new Intl.DateTimeFormat("en-GB", {
37
- timeZone: answerZone,
38
- ...options,
39
- }).format(at);
40
- const formatted = format === "human"
41
- ? parts({ dateStyle: "full", timeStyle: "short" })
42
- : format === "date"
43
- ? parts({ year: "numeric", month: "2-digit", day: "2-digit" })
44
- : format === "time"
45
- ? parts({
46
- hour: "2-digit",
47
- minute: "2-digit",
48
- second: "2-digit",
49
- hour12: false,
50
- })
51
- : at.toISOString();
52
- return {
53
- now: formatted,
54
- iso: at.toISOString(),
55
- timezone: answerZone,
56
- unix: Math.floor(at.getTime() / 1000),
57
- weekday: parts({ weekday: "long" }),
58
- };
59
- },
60
- });
61
- }
1
+ import{tool as m}from"../../tool/index.js";function h(i={}){const r=i.now??(()=>new Date);return m({name:"current_time",effect:"readonly",description:"The current date and time. Call this before answering anything that depends on today's date \u2014 ages, deadlines, 'how long ago', 'this year'. Do not rely on your own sense of the date; it is the date you were trained, not today.",args:[{name:"timezone",type:"string",required:!1,description:"IANA zone such as Europe/London or Asia/Kolkata. Defaults to the server's zone."},{name:"format",type:"enum",required:!1,options:["iso","human","date","time"],description:"iso is unambiguous and the right default for anything machine-read."}],run:({timezone:s,format:e="iso"})=>{const t=r(),o=s??i.defaultTimeZone??Intl.DateTimeFormat().resolvedOptions().timeZone;try{new Intl.DateTimeFormat("en-GB",{timeZone:o})}catch{throw new Error(`"${o}" is not a known IANA time zone.`)}const a=e==="iso"?"UTC":o,n=d=>new Intl.DateTimeFormat("en-GB",{timeZone:a,...d}).format(t);return{now:e==="human"?n({dateStyle:"full",timeStyle:"short"}):e==="date"?n({year:"numeric",month:"2-digit",day:"2-digit"}):e==="time"?n({hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1}):t.toISOString(),iso:t.toISOString(),timezone:a,unix:Math.floor(t.getTime()/1e3),weekday:n({weekday:"long"})}}})}export{h as currentTime};
@@ -1 +1 @@
1
- export { currentTime } from "./clock.js";
1
+ import{currentTime as m}from"./clock.js";export{m as currentTime};
@@ -0,0 +1,28 @@
1
+ export interface SandboxRequest {
2
+ code: string;
3
+ language: string;
4
+ timeoutMs: number;
5
+ }
6
+ export interface SandboxResult {
7
+ ok: boolean;
8
+ stdout: string;
9
+ stderr?: string;
10
+ timedOut?: boolean;
11
+ costMicroUsd?: number;
12
+ }
13
+ export interface Sandbox {
14
+ execute(request: SandboxRequest): Promise<SandboxResult>;
15
+ }
16
+ export interface CodeExecOptions {
17
+ sandbox: Sandbox;
18
+ language?: string;
19
+ timeoutMs?: number;
20
+ maxChars?: number;
21
+ }
22
+ export declare function codeExec(opts: CodeExecOptions): import("../../index.js").Tool<import("../../index.js").ArgsOf<readonly [{
23
+ readonly name: "code";
24
+ readonly type: "text";
25
+ readonly required: true;
26
+ readonly maxLength: 10000;
27
+ readonly description: `A complete ${string} program that prints its result.`;
28
+ }]>>;
@@ -0,0 +1 @@
1
+ import{tool as d}from"../../tool/index.js";const u="python",l=1e4,h=2e4,m=1e4;function i(e,t){return{text:e.slice(0,t),overflowed:e.length>t}}function x(e){if(!e.sandbox)throw new Error("codeExec needs a sandbox to execute in, and there is no default: this package ships none, because running model-written code in this process is arbitrary code execution with extra steps.");const t=e.language??u,o=e.timeoutMs??l,r=e.maxChars??h;return d({name:"code_exec",description:`Runs a short ${t} program in a sandbox and hands back what it printed. Use it for work an arithmetic expression cannot state \u2014 sorting, filtering, joining, parsing or summing data you already have. Print the answer; a value left unprinted comes back as nothing. There is no network and no filesystem, nothing survives between calls, and a run longer than ${o}ms is cut off.`,args:[{name:"code",type:"text",required:!0,maxLength:m,description:`A complete ${t} program that prints its result.`}],run:async({code:c})=>{const n=await e.sandbox.execute({code:c,language:t,timeoutMs:o}),s=i(n.stdout??"",r),a=i(n.stderr??"",r);return{ok:n.ok,stdout:s.text,stderr:a.text,truncated:s.overflowed||a.overflowed,timedOut:n.timedOut===!0,...n.costMicroUsd===void 0?{}:{costMicroUsd:n.costMicroUsd}}}})}export{x as codeExec};
@@ -0,0 +1 @@
1
+ export { type CodeExecOptions, codeExec, type Sandbox, type SandboxRequest, type SandboxResult, } from "./code-exec.js";
@@ -0,0 +1 @@
1
+ import{codeExec as c}from"./code-exec.js";export{c as codeExec};
@@ -1,41 +1 @@
1
- import { tool } from "../../tool/index.js";
2
- import { between, parseDate, shift } from "./shift.js";
3
- const UNITS = ["seconds", "minutes", "hours", "days", "weeks", "months", "years"];
4
- export function dateMath() {
5
- return tool({
6
- name: "date_math",
7
- effect: "readonly",
8
- description: "Works out a date exactly. `add` and `subtract` move a date by an amount of a " +
9
- "unit; `difference` says how far apart two dates are in whole units. Use it for " +
10
- "deadlines, notice periods, ages and 'how long ago' — never work a date out yourself.",
11
- args: [
12
- {
13
- name: "operation",
14
- type: "enum",
15
- required: true,
16
- options: ["add", "subtract", "difference"],
17
- },
18
- { name: "date", type: "string", required: true, description: "ISO, as in 2026-03-31." },
19
- { name: "unit", type: "enum", required: true, options: [...UNITS] },
20
- { name: "amount", type: "number", required: false, description: "For add and subtract." },
21
- {
22
- name: "to",
23
- type: "string",
24
- required: false,
25
- description: "The other date, for difference.",
26
- },
27
- ],
28
- run: ({ operation, date, unit, amount, to }) => {
29
- const from = parseDate(date, "date");
30
- if (operation === "difference") {
31
- if (!to)
32
- throw new Error("difference needs a second date in `to`.");
33
- return { unit, difference: between(from, parseDate(to, "to"), unit) };
34
- }
35
- if (amount === undefined)
36
- throw new Error(`${operation} needs an \`amount\`.`);
37
- const moved = shift(from, operation === "subtract" ? -amount : amount, unit);
38
- return { iso: moved.toISOString(), date: moved.toISOString().slice(0, 10) };
39
- },
40
- });
41
- }
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,2 +1 @@
1
- export { dateMath } from "./date.js";
2
- export { between, parseDate, shift } from "./shift.js";
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,47 +1 @@
1
- const MS = {
2
- seconds: 1000,
3
- minutes: 60_000,
4
- hours: 3_600_000,
5
- days: 86_400_000,
6
- weeks: 604_800_000,
7
- };
8
- export function parseDate(value, label) {
9
- const at = new Date(value);
10
- if (!Number.isFinite(at.getTime())) {
11
- throw new Error(`${label} "${value}" is not a date. Use an ISO instant, as in 2026-03-31.`);
12
- }
13
- return at;
14
- }
15
- function addMonths(from, months) {
16
- const day = from.getUTCDate();
17
- const shifted = new Date(from.getTime());
18
- shifted.setUTCDate(1);
19
- shifted.setUTCMonth(shifted.getUTCMonth() + months);
20
- const lastOfMonth = new Date(Date.UTC(shifted.getUTCFullYear(), shifted.getUTCMonth() + 1, 0)).getUTCDate();
21
- shifted.setUTCDate(Math.min(day, lastOfMonth));
22
- return shifted;
23
- }
24
- export function shift(from, amount, unit) {
25
- if (!Number.isInteger(amount))
26
- throw new Error("amount must be a whole number.");
27
- if (unit === "months")
28
- return addMonths(from, amount);
29
- if (unit === "years")
30
- return addMonths(from, amount * 12);
31
- const ms = MS[unit];
32
- if (!ms)
33
- throw new Error(`"${unit}" is not a unit this tool knows.`);
34
- return new Date(from.getTime() + amount * ms);
35
- }
36
- export function between(from, to, unit) {
37
- if (unit === "months" || unit === "years") {
38
- const months = (to.getUTCFullYear() - from.getUTCFullYear()) * 12 +
39
- (to.getUTCMonth() - from.getUTCMonth()) -
40
- (to.getUTCDate() < from.getUTCDate() ? 1 : 0);
41
- return unit === "years" ? Math.trunc(months / 12) : months;
42
- }
43
- const ms = MS[unit];
44
- if (!ms)
45
- throw new Error(`"${unit}" is not a unit this tool knows.`);
46
- return Math.trunc((to.getTime() - from.getTime()) / ms);
47
- }
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,7 +1,44 @@
1
- import type { LocusGraphClient } from "@locusgraph/client";
1
+ /** One piece of evidence, with where it came from. */
2
+ export interface RecalledFact {
3
+ text: string;
4
+ locus: string;
5
+ contexts: string[];
6
+ score: number;
7
+ source?: string;
8
+ }
9
+ /** What a deep recall answered, and what answering it cost. */
10
+ export interface RecallResult {
11
+ facts: RecalledFact[];
12
+ contexts_walked: string[];
13
+ summary?: string;
14
+ coverage: "full" | "partial";
15
+ fallback: boolean;
16
+ usage: {
17
+ cost_usd: number;
18
+ };
19
+ }
20
+ /**
21
+ * Anything that can answer a deep recall — a `LocusGraphClient`, or a test
22
+ * double.
23
+ *
24
+ * Declared structurally rather than imported, for the reason `prompt` declares
25
+ * its own `Recordable` and `llms` reads provider replies as plain objects: a
26
+ * type-only import still puts `@locusgraph/client` in `dependencies`, so every
27
+ * install of this package downloaded a client that nothing here ever loads, and
28
+ * every consumer's typecheck needed it resolvable to read one `.d.ts`. A real
29
+ * client satisfies this, which `tests/structural.test.ts` proves with the
30
+ * package still on hand as a devDependency.
31
+ */
32
+ export interface Recaller {
33
+ deepRecall(query: {
34
+ question: string;
35
+ graphId?: string;
36
+ contextIds?: string[];
37
+ }): Promise<RecallResult>;
38
+ }
2
39
  export interface DeepRecallOptions {
3
40
  /** A `LocusGraphClient`, or anything carrying its `deepRecall`. */
4
- client: Pick<LocusGraphClient, "deepRecall">;
41
+ client: Recaller;
5
42
  /** Graph the walk runs over. Unset, the client's own default is used. */
6
43
  graphId?: string;
7
44
  /** Longest evidence list handed back. Default 20. */
@@ -1,45 +1 @@
1
- import { tool } from "../../tool/index.js";
2
- const DEFAULT_MAX_FACTS = 20;
3
- export function deepRecall(opts) {
4
- const maxFacts = opts.maxFacts ?? DEFAULT_MAX_FACTS;
5
- return tool({
6
- name: "deep_recall",
7
- description: "Searches your own knowledge base and returns an answer with the evidence it rests " +
8
- "on. A walker follows linked contexts, so it answers questions spread across several " +
9
- "notes — what do we know about X, what was decided and why. Ask one focused thing. " +
10
- "Prefer it over a web search whenever the answer could already be in your records.",
11
- effect: "readonly",
12
- args: [
13
- {
14
- name: "question",
15
- type: "string",
16
- required: true,
17
- maxLength: 1000,
18
- description: "One focused question, not a conversation and not several at once.",
19
- },
20
- {
21
- name: "context_ids",
22
- type: "list",
23
- required: false,
24
- description: "Contexts to start the walk from, as type:name — take them from the contexts of " +
25
- "an earlier result. A hint, not a filter; the walk may go elsewhere.",
26
- },
27
- ],
28
- run: async ({ question, context_ids }) => {
29
- const result = await opts.client.deepRecall({
30
- question,
31
- ...(opts.graphId ? { graphId: opts.graphId } : {}),
32
- ...(context_ids?.length ? { contextIds: context_ids } : {}),
33
- });
34
- return {
35
- summary: result.summary,
36
- facts: result.facts.slice(0, maxFacts),
37
- truncated: result.facts.length > maxFacts,
38
- contextsWalked: result.contexts_walked,
39
- coverage: result.coverage,
40
- fallback: result.fallback,
41
- costMicroUsd: Math.round(result.usage.cost_usd * 1_000_000),
42
- };
43
- },
44
- });
45
- }
1
+ import{tool as s}from"../../tool/index.js";const o=20;function l(t){const a=t.maxFacts??o;return s({name:"deep_recall",description:"Searches your own knowledge base and returns an answer with the evidence it rests on. A walker follows linked contexts, so it answers questions spread across several notes \u2014 what do we know about X, what was decided and why. Ask one focused thing. Prefer it over a web search whenever the answer could already be in your records.",effect:"readonly",args:[{name:"question",type:"string",required:!0,maxLength:1e3,description:"One focused question, not a conversation and not several at once."},{name:"context_ids",type:"list",required:!1,description:"Contexts to start the walk from, as type:name \u2014 take them from the contexts of an earlier result. A hint, not a filter; the walk may go elsewhere."}],run:async({question:n,context_ids:r})=>{const e=await t.client.deepRecall({question:n,...t.graphId?{graphId:t.graphId}:{},...r?.length?{contextIds:r}:{}});return{summary:e.summary,facts:e.facts.slice(0,a),truncated:e.facts.length>a,contextsWalked:e.contexts_walked,coverage:e.coverage,fallback:e.fallback,costMicroUsd:Math.round(e.usage.cost_usd*1e6)}}})}export{l as deepRecall};
@@ -1 +1 @@
1
- export { type DeepRecallOptions, deepRecall } from "./deep-recall.js";
1
+ export { type DeepRecallOptions, deepRecall, type RecalledFact, type Recaller, type RecallResult, } from "./deep-recall.js";
@@ -1 +1 @@
1
- export { deepRecall } from "./deep-recall.js";
1
+ import{deepRecall as o}from"./deep-recall.js";export{o as deepRecall};
@@ -1,20 +1 @@
1
- import { tool } from "../../tool/index.js";
2
- export function escalate(opts = {}) {
3
- return tool({
4
- name: "escalate",
5
- description: "Hand this over to a person. Call it when you are missing something you cannot " +
6
- "get, when the request needs an authority you do not have, or when acting on a " +
7
- "guess would be worse than waiting. Say plainly why.",
8
- pinned: opts.pinned ?? true,
9
- args: [
10
- { name: "reason", type: "text", required: true, maxLength: 2000 },
11
- { name: "summary", type: "text", required: false, maxLength: 4000 },
12
- { name: "urgency", type: "enum", required: false, options: ["low", "normal", "high"] },
13
- ],
14
- run: async ({ reason, summary, urgency = "normal" }) => {
15
- const request = { reason, summary, urgency };
16
- const handled = opts.onEscalate ? await opts.onEscalate(request) : undefined;
17
- return { escalated: true, delivered: Boolean(opts.onEscalate), request, handled };
18
- },
19
- });
20
- }
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 +1 @@
1
- export { escalate } from "./escalate.js";
1
+ import{escalate as o}from"./escalate.js";export{o as escalate};
@@ -1,26 +1 @@
1
- import { tool } from "../../tool/index.js";
2
- export function isFinished(result) {
3
- if (result.name !== "finish" || result.status !== "completed")
4
- return false;
5
- try {
6
- return JSON.parse(result.output).done === true;
7
- }
8
- catch {
9
- return false;
10
- }
11
- }
12
- export function answerOf(result) {
13
- if (!isFinished(result))
14
- return null;
15
- return JSON.parse(result.output).answer;
16
- }
17
- export function finish() {
18
- return tool({
19
- name: "finish",
20
- effect: "readonly",
21
- description: "Call this when you have the answer and need no more tools. Pass the final " +
22
- "answer as it should be given to the person who asked.",
23
- args: [{ name: "answer", type: "text", required: true, maxLength: 20_000 }],
24
- run: ({ answer }) => ({ done: true, answer }),
25
- });
26
- }
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};