@spendgraph/tools 0.5.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.
- package/LICENSE +202 -0
- package/README.md +25 -258
- package/dist/bogus/ask-mum.d.ts +5 -0
- package/dist/bogus/ask-mum.js +1 -0
- package/dist/bogus/book-taxi.d.ts +9 -0
- package/dist/bogus/book-taxi.js +1 -0
- package/dist/bogus/bus.d.ts +8 -0
- package/dist/bogus/bus.js +1 -0
- package/dist/bogus/calculator.d.ts +14 -0
- package/dist/bogus/calculator.js +1 -0
- package/dist/bogus/dictionary.d.ts +6 -0
- package/dist/bogus/dictionary.js +1 -0
- package/dist/bogus/feed-cat.d.ts +8 -0
- package/dist/bogus/feed-cat.js +1 -0
- package/dist/bogus/football-score.d.ts +6 -0
- package/dist/bogus/football-score.js +1 -0
- package/dist/bogus/gold-price.d.ts +7 -0
- package/dist/bogus/gold-price.js +1 -0
- package/dist/bogus/index.d.ts +14 -0
- package/dist/bogus/index.js +1 -0
- package/dist/bogus/my-calendar.d.ts +6 -0
- package/dist/bogus/my-calendar.js +1 -0
- package/dist/bogus/my-notes.d.ts +6 -0
- package/dist/bogus/my-notes.js +1 -0
- package/dist/bogus/my-recipes.d.ts +6 -0
- package/dist/bogus/my-recipes.js +1 -0
- package/dist/bogus/order-pizza.d.ts +12 -0
- package/dist/bogus/order-pizza.js +1 -0
- package/dist/bogus/prayer-times.d.ts +6 -0
- package/dist/bogus/prayer-times.js +1 -0
- package/dist/bogus/receipt.d.ts +8 -0
- package/dist/bogus/receipt.js +1 -0
- package/dist/builtin/calculate/calculate.js +1 -21
- package/dist/builtin/calculate/exact.d.ts +16 -0
- package/dist/builtin/calculate/exact.js +1 -0
- package/dist/builtin/calculate/index.js +1 -2
- package/dist/builtin/calculate/parse.js +1 -137
- package/dist/builtin/clock/clock.js +1 -61
- package/dist/builtin/clock/index.js +1 -1
- package/dist/builtin/code-exec/code-exec.d.ts +28 -0
- package/dist/builtin/code-exec/code-exec.js +1 -0
- package/dist/builtin/code-exec/index.d.ts +1 -0
- package/dist/builtin/code-exec/index.js +1 -0
- package/dist/builtin/date/date.js +1 -41
- package/dist/builtin/date/index.js +1 -2
- package/dist/builtin/date/shift.js +1 -47
- package/dist/builtin/deep-recall/deep-recall.d.ts +39 -2
- package/dist/builtin/deep-recall/deep-recall.js +1 -45
- package/dist/builtin/deep-recall/index.d.ts +1 -1
- package/dist/builtin/deep-recall/index.js +1 -1
- package/dist/builtin/escalate/escalate.js +1 -20
- package/dist/builtin/escalate/index.js +1 -1
- package/dist/builtin/finish/finish.js +1 -26
- package/dist/builtin/finish/index.js +1 -1
- package/dist/builtin/http/allow.js +1 -37
- package/dist/builtin/http/http.js +1 -41
- package/dist/builtin/http/index.js +1 -2
- package/dist/builtin/index.d.ts +2 -1
- package/dist/builtin/index.js +1 -11
- package/dist/builtin/json/index.js +1 -2
- package/dist/builtin/json/json.js +1 -37
- package/dist/builtin/json/path.js +1 -56
- package/dist/builtin/think/index.js +1 -1
- package/dist/builtin/think/think.js +1 -12
- package/dist/builtin/web-search/index.js +1 -1
- package/dist/builtin/web-search/reply.js +1 -27
- package/dist/builtin/web-search/web-search.d.ts +6 -1
- package/dist/builtin/web-search/web-search.js +1 -103
- package/dist/builtin/write-document/commission.js +13 -152
- package/dist/builtin/write-document/design.js +5 -86
- package/dist/builtin/write-document/http.js +1 -71
- package/dist/builtin/write-document/index.js +1 -4
- package/dist/builtin/write-document/types.js +0 -1
- package/dist/builtin/write-document/write-document.js +1 -142
- package/dist/bus/args.d.ts +11 -0
- package/dist/bus/args.js +1 -0
- package/dist/bus/bus.js +1 -49
- package/dist/bus/group.d.ts +8 -0
- package/dist/bus/group.js +1 -0
- package/dist/bus/index.d.ts +1 -0
- package/dist/bus/index.js +1 -2
- package/dist/bus/invoke.js +1 -43
- package/dist/bus/options.js +0 -1
- package/dist/bus/output.d.ts +26 -0
- package/dist/bus/output.js +2 -0
- package/dist/bus/overlap.js +1 -31
- package/dist/bus/score.d.ts +11 -9
- package/dist/bus/score.js +1 -12
- package/dist/bus/select.d.ts +2 -1
- package/dist/bus/select.js +1 -44
- package/dist/bus/step.js +1 -11
- package/dist/bus/terms.d.ts +25 -0
- package/dist/bus/terms.js +1 -0
- package/dist/bus/trace.js +1 -22
- package/dist/index.d.ts +13 -3
- package/dist/index.js +1 -3
- package/dist/internals.d.ts +1 -1
- package/dist/internals.js +1 -10
- package/dist/tool/bind.js +1 -64
- package/dist/tool/index.js +1 -2
- package/dist/tool/tool.d.ts +1 -0
- package/dist/tool/tool.js +1 -51
- package/dist/types/index.js +0 -1
- package/dist/types/result.js +0 -1
- package/dist/types/tool.d.ts +13 -0
- package/dist/types/tool.js +0 -1
- package/dist/wire/index.js +1 -3
- package/dist/wire/json-schema.js +1 -65
- package/dist/wire/markdown.d.ts +5 -3
- package/dist/wire/markdown.js +9 -29
- package/dist/wire/providers.js +1 -18
- package/package.json +11 -7
|
@@ -1,86 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
accent: "#2563EB",
|
|
7
|
-
text: "#111827",
|
|
8
|
-
muted: "#64748B",
|
|
9
|
-
surface: "#FFFFFF",
|
|
10
|
-
},
|
|
11
|
-
layout: "A4 portrait, 20mm margins, a single text column at 60-75 characters, a 4pt spacing scale, " +
|
|
12
|
-
"and one heading scale used consistently (28/20/16/12). Whitespace is the design.",
|
|
13
|
-
charts: "One message per figure, stated in its caption. Label series directly where there are three " +
|
|
14
|
-
"or fewer. No 3D, no gradients, no gridline heavier than the data, no pie chart past three " +
|
|
15
|
-
"slices, and axes that start at zero unless the caption says why not.",
|
|
16
|
-
tone: "Plain and specific. Lead with the finding, then the evidence. No filler adjectives, no " +
|
|
17
|
-
"throat-clearing, no sentence that survives being deleted.",
|
|
18
|
-
rules: [
|
|
19
|
-
"A cover carrying the title, date and author, and no other ornament.",
|
|
20
|
-
"Every number traceable to something in the source material; invent nothing.",
|
|
21
|
-
"Tables for anything being compared; prose for anything being argued.",
|
|
22
|
-
"No stock imagery, no clip art, no placeholder text in the finished file.",
|
|
23
|
-
"Page numbers on every page after the cover.",
|
|
24
|
-
],
|
|
25
|
-
};
|
|
26
|
-
const FORMAT_CRAFT = {
|
|
27
|
-
pdf: "Cover, then contents if it runs past four pages, then the body. Figures captioned and " +
|
|
28
|
-
"referenced from the text. Nothing straddles a page break: no orphan heading, no split table row.",
|
|
29
|
-
pptx: "16:9. One idea per slide, and the title is that idea as a sentence — a claim, not a label. " +
|
|
30
|
-
"At most six lines of body per slide, one figure per slide at most, and everything you cut " +
|
|
31
|
-
"goes into the speaker notes. Close on decisions and next steps, not on 'thank you'.",
|
|
32
|
-
docx: "Real Word styles for headings rather than manual bold, a table-of-contents field rather " +
|
|
33
|
-
"than a typed list, a header row on every table, and captions on figures.",
|
|
34
|
-
md: "A title heading, ATX headings throughout, tables for comparisons, fenced code with a language tag, and no HTML.",
|
|
35
|
-
};
|
|
36
|
-
function mergeGroup(base, over) {
|
|
37
|
-
if (!base)
|
|
38
|
-
return over;
|
|
39
|
-
if (!over)
|
|
40
|
-
return base;
|
|
41
|
-
return { ...base, ...over };
|
|
42
|
-
}
|
|
43
|
-
export function mergeDesign(base, over) {
|
|
44
|
-
if (!over)
|
|
45
|
-
return base;
|
|
46
|
-
return {
|
|
47
|
-
...base,
|
|
48
|
-
...over,
|
|
49
|
-
fonts: mergeGroup(base.fonts, over.fonts),
|
|
50
|
-
palette: mergeGroup(base.palette, over.palette),
|
|
51
|
-
rules: [...(base.rules ?? []), ...(over.rules ?? [])],
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
const line = (label, value) => (value ? [`${label}: ${value}`] : []);
|
|
55
|
-
export function renderDesign(guide, format, notes) {
|
|
56
|
-
const { fonts, palette } = guide;
|
|
57
|
-
const parts = [
|
|
58
|
-
...line("Style", guide.name),
|
|
59
|
-
...line("Typefaces", [
|
|
60
|
-
fonts?.heading && `headings ${fonts.heading}`,
|
|
61
|
-
fonts?.body && `body ${fonts.body}`,
|
|
62
|
-
fonts?.mono && `code ${fonts.mono}`,
|
|
63
|
-
]
|
|
64
|
-
.filter(Boolean)
|
|
65
|
-
.join(", ") || undefined),
|
|
66
|
-
...line("Palette", [
|
|
67
|
-
palette?.primary && `primary ${palette.primary}`,
|
|
68
|
-
palette?.accent && `accent ${palette.accent}`,
|
|
69
|
-
palette?.text && `text ${palette.text}`,
|
|
70
|
-
palette?.muted && `muted ${palette.muted}`,
|
|
71
|
-
palette?.surface && `surface ${palette.surface}`,
|
|
72
|
-
]
|
|
73
|
-
.filter(Boolean)
|
|
74
|
-
.join(", ") || undefined),
|
|
75
|
-
...line("Layout", guide.layout),
|
|
76
|
-
...line("Figures", guide.charts),
|
|
77
|
-
...line("Voice", guide.tone),
|
|
78
|
-
...line("Logo", guide.logoUrl),
|
|
79
|
-
`Format craft (.${format}): ${FORMAT_CRAFT[format]}`,
|
|
80
|
-
...(guide.rules?.length ? [`Rules:\n${guide.rules.map((r) => `- ${r}`).join("\n")}`] : []),
|
|
81
|
-
...(notes?.trim()
|
|
82
|
-
? [`Overrides for this document, which win over everything above:\n${notes.trim()}`]
|
|
83
|
-
: []),
|
|
84
|
-
];
|
|
85
|
-
return parts.join("\n");
|
|
86
|
-
}
|
|
1
|
+
const c={name:"Spendgraph house style",fonts:{heading:"Inter",body:"Inter",mono:"JetBrains Mono"},palette:{primary:"#0F172A",accent:"#2563EB",text:"#111827",muted:"#64748B",surface:"#FFFFFF"},layout:"A4 portrait, 20mm margins, a single text column at 60-75 characters, a 4pt spacing scale, and one heading scale used consistently (28/20/16/12). Whitespace is the design.",charts:"One message per figure, stated in its caption. Label series directly where there are three or fewer. No 3D, no gradients, no gridline heavier than the data, no pie chart past three slices, and axes that start at zero unless the caption says why not.",tone:"Plain and specific. Lead with the finding, then the evidence. No filler adjectives, no throat-clearing, no sentence that survives being deleted.",rules:["A cover carrying the title, date and author, and no other ornament.","Every number traceable to something in the source material; invent nothing.","Tables for anything being compared; prose for anything being argued.","No stock imagery, no clip art, no placeholder text in the finished file.","Page numbers on every page after the cover."]},d={pdf:"Cover, then contents if it runs past four pages, then the body. Figures captioned and referenced from the text. Nothing straddles a page break: no orphan heading, no split table row.",pptx:"16:9. One idea per slide, and the title is that idea as a sentence \u2014 a claim, not a label. At most six lines of body per slide, one figure per slide at most, and everything you cut goes into the speaker notes. Close on decisions and next steps, not on 'thank you'.",docx:"Real Word styles for headings rather than manual bold, a table-of-contents field rather than a typed list, a header row on every table, and captions on figures.",md:"A title heading, ATX headings throughout, tables for comparisons, fenced code with a language tag, and no HTML."};function i(e,t){return e?t?{...e,...t}:e:t}function h(e,t){return t?{...e,...t,fonts:i(e.fonts,t.fonts),palette:i(e.palette,t.palette),rules:[...e.rules??[],...t.rules??[]]}:e}const a=(e,t)=>t?[`${e}: ${t}`]:[];function p(e,t,o){const{fonts:r,palette:n}=e;return[...a("Style",e.name),...a("Typefaces",[r?.heading&&`headings ${r.heading}`,r?.body&&`body ${r.body}`,r?.mono&&`code ${r.mono}`].filter(Boolean).join(", ")||void 0),...a("Palette",[n?.primary&&`primary ${n.primary}`,n?.accent&&`accent ${n.accent}`,n?.text&&`text ${n.text}`,n?.muted&&`muted ${n.muted}`,n?.surface&&`surface ${n.surface}`].filter(Boolean).join(", ")||void 0),...a("Layout",e.layout),...a("Figures",e.charts),...a("Voice",e.tone),...a("Logo",e.logoUrl),`Format craft (.${t}): ${d[t]}`,...e.rules?.length?[`Rules:
|
|
2
|
+
${e.rules.map(s=>`- ${s}`).join(`
|
|
3
|
+
`)}`]:[],...o?.trim()?[`Overrides for this document, which win over everything above:
|
|
4
|
+
${o.trim()}`]:[]].join(`
|
|
5
|
+
`)}export{c as DEFAULT_DESIGN,h as mergeDesign,p as renderDesign};
|
|
@@ -1,71 +1 @@
|
|
|
1
|
-
|
|
2
|
-
const DEFAULT_REQUEST_TIMEOUT_MS = 60_000;
|
|
3
|
-
const ERROR_DETAIL_CHARS = 500;
|
|
4
|
-
export class MoaApiError extends Error {
|
|
5
|
-
status;
|
|
6
|
-
detail;
|
|
7
|
-
constructor(message, status, detail) {
|
|
8
|
-
super(detail ? `${message}: ${detail}` : message);
|
|
9
|
-
this.name = "MoaApiError";
|
|
10
|
-
this.status = status;
|
|
11
|
-
this.detail = detail;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
function reasonOf(err) {
|
|
15
|
-
const cause = err.cause;
|
|
16
|
-
return cause?.code ?? cause?.message ?? err.message;
|
|
17
|
-
}
|
|
18
|
-
function deadlineSignal(timeoutMs, caller) {
|
|
19
|
-
const controller = new AbortController();
|
|
20
|
-
const stop = () => controller.abort();
|
|
21
|
-
let expired = false;
|
|
22
|
-
const timer = setTimeout(() => {
|
|
23
|
-
expired = true;
|
|
24
|
-
stop();
|
|
25
|
-
}, timeoutMs);
|
|
26
|
-
caller?.addEventListener("abort", stop, { once: true });
|
|
27
|
-
if (caller?.aborted)
|
|
28
|
-
stop();
|
|
29
|
-
return {
|
|
30
|
-
signal: controller.signal,
|
|
31
|
-
expired: () => expired,
|
|
32
|
-
release: () => {
|
|
33
|
-
clearTimeout(timer);
|
|
34
|
-
caller?.removeEventListener("abort", stop);
|
|
35
|
-
},
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
export function createRequest(config = {}) {
|
|
39
|
-
const doFetch = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
40
|
-
const base = (config.baseUrl ?? MOA_BASE_URL).replace(/\/+$/, "");
|
|
41
|
-
return async function request(path, opts = {}) {
|
|
42
|
-
const timeoutMs = opts.timeoutMs ?? config.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
43
|
-
const method = opts.method ?? "GET";
|
|
44
|
-
const url = `${base}${path}`;
|
|
45
|
-
const { signal, expired, release } = deadlineSignal(timeoutMs, opts.signal);
|
|
46
|
-
try {
|
|
47
|
-
const res = await doFetch(url, {
|
|
48
|
-
method,
|
|
49
|
-
signal,
|
|
50
|
-
headers: {
|
|
51
|
-
...(config.apiKey ? { authorization: `Bearer ${config.apiKey}` } : {}),
|
|
52
|
-
...(opts.body === undefined ? {} : { "content-type": "application/json" }),
|
|
53
|
-
},
|
|
54
|
-
...(opts.body === undefined ? {} : { body: JSON.stringify(opts.body) }),
|
|
55
|
-
}).catch((err) => {
|
|
56
|
-
if (opts.signal?.aborted)
|
|
57
|
-
throw err;
|
|
58
|
-
if (expired())
|
|
59
|
-
throw new MoaApiError(`Moa did not answer ${method} ${path}`, 504, `${timeoutMs}ms`);
|
|
60
|
-
throw new MoaApiError(`Moa is unreachable at ${url}`, 0, reasonOf(err));
|
|
61
|
-
});
|
|
62
|
-
if (!res.ok) {
|
|
63
|
-
throw new MoaApiError(`Moa returned ${res.status} for ${method} ${path}`, res.status, (await res.text()).slice(0, ERROR_DETAIL_CHARS));
|
|
64
|
-
}
|
|
65
|
-
return res.status === 204 ? undefined : (await res.json());
|
|
66
|
-
}
|
|
67
|
-
finally {
|
|
68
|
-
release();
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
}
|
|
1
|
+
const f="https://api.fnmoa.com",M=6e4,_=500;class c extends Error{status;detail;constructor(t,s,n){super(n?`${t}: ${n}`:t),this.name="MoaApiError",this.status=s,this.detail=n}}function T(e){const t=e.cause;return t?.code??t?.message??e.message}function m(e,t){const s=new AbortController,n=()=>s.abort();let r=!1;const o=setTimeout(()=>{r=!0,n()},e);return t?.addEventListener("abort",n,{once:!0}),t?.aborted&&n(),{signal:s.signal,expired:()=>r,release:()=>{clearTimeout(o),t?.removeEventListener("abort",n)}}}function $(e={}){const t=e.fetch??globalThis.fetch.bind(globalThis),s=(e.baseUrl??f).replace(/\/+$/,"");return async function(r,o={}){const u=o.timeoutMs??e.timeoutMs??6e4,i=o.method??"GET",d=`${s}${r}`,{signal:E,expired:h,release:b}=m(u,o.signal);try{const a=await t(d,{method:i,signal:E,headers:{...e.apiKey?{authorization:`Bearer ${e.apiKey}`}:{},...o.body===void 0?{}:{"content-type":"application/json"}},...o.body===void 0?{}:{body:JSON.stringify(o.body)}}).catch(l=>{throw o.signal?.aborted?l:h()?new c(`Moa did not answer ${i} ${r}`,504,`${u}ms`):new c(`Moa is unreachable at ${d}`,0,T(l))});if(!a.ok)throw new c(`Moa returned ${a.status} for ${i} ${r}`,a.status,(await a.text()).slice(0,500));return a.status===204?void 0:await a.json()}finally{b()}}}export{f as MOA_BASE_URL,c as MoaApiError,$ as createRequest};
|
|
@@ -1,4 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { DEFAULT_DESIGN, mergeDesign, renderDesign } from "./design.js";
|
|
3
|
-
export { MOA_BASE_URL, MoaApiError } from "./http.js";
|
|
4
|
-
export { writeDocument } from "./write-document.js";
|
|
1
|
+
import{CommissionAbortedError as e,CommissionTimeoutError as i,createCommissionClient as m,INTERACTIVE_TIMEOUT_MS as t,pollUntilDone as n}from"./commission.js";import{DEFAULT_DESIGN as s,mergeDesign as p,renderDesign as A}from"./design.js";import{MOA_BASE_URL as T,MoaApiError as C}from"./http.js";import{writeDocument as f}from"./write-document.js";export{e as CommissionAbortedError,i as CommissionTimeoutError,s as DEFAULT_DESIGN,t as INTERACTIVE_TIMEOUT_MS,T as MOA_BASE_URL,C as MoaApiError,m as createCommissionClient,p as mergeDesign,n as pollUntilDone,A as renderDesign,f as writeDocument};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,142 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { CommissionAbortedError, CommissionTimeoutError, createCommissionClient, pollUntilDone, } from "./commission.js";
|
|
3
|
-
import { DEFAULT_DESIGN, mergeDesign, renderDesign } from "./design.js";
|
|
4
|
-
const ALL_FORMATS = ["pdf", "pptx", "docx", "md"];
|
|
5
|
-
const DEFAULT_TIMEOUT_MS = 600_000;
|
|
6
|
-
const DEFAULT_SETTLE_MS = 2_000;
|
|
7
|
-
const DEFAULT_MAX_CHARS = 4_000;
|
|
8
|
-
function describe(formats, design) {
|
|
9
|
-
return ("Commissions a document agent to write and render a finished file — " +
|
|
10
|
-
`${formats.map((f) => `.${f}`).join(", ")} — and hands back where to download it. Use it ` +
|
|
11
|
-
"when the answer has to be a document somebody opens: a report, a deck, a proposal, a " +
|
|
12
|
-
"one-pager. Not for text you can simply write in your reply. It runs for minutes and it " +
|
|
13
|
-
`costs money, so brief it once and brief it fully — the agent cannot see this ` +
|
|
14
|
-
"conversation, so every fact, figure and quote it must use goes in `source`. Design is " +
|
|
15
|
-
`already settled by ${design.name ?? "the house style"}; use \`design_notes\` only for what ` +
|
|
16
|
-
"this one document must do differently.");
|
|
17
|
-
}
|
|
18
|
-
function delivered(result, style, maxChars, includeData) {
|
|
19
|
-
const documents = (result.artifacts ?? []).map((a) => ({
|
|
20
|
-
name: a.name,
|
|
21
|
-
format: a.format,
|
|
22
|
-
url: a.url,
|
|
23
|
-
mime: a.mime,
|
|
24
|
-
sizeBytes: a.sizeBytes,
|
|
25
|
-
...(includeData && a.data ? { data: a.data } : {}),
|
|
26
|
-
}));
|
|
27
|
-
return {
|
|
28
|
-
taskId: result.id,
|
|
29
|
-
sessionId: result.sessionId,
|
|
30
|
-
status: "done",
|
|
31
|
-
style,
|
|
32
|
-
documents,
|
|
33
|
-
delivered: documents.length > 0,
|
|
34
|
-
writeUp: result.output?.slice(0, maxChars),
|
|
35
|
-
truncated: (result.output?.length ?? 0) > maxChars,
|
|
36
|
-
turns: result.numTurns,
|
|
37
|
-
costMicroUsd: result.costUsd === undefined ? undefined : Math.round(result.costUsd * 1_000_000),
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
function stillRendering(id, style, err) {
|
|
41
|
-
return {
|
|
42
|
-
taskId: id,
|
|
43
|
-
status: "running",
|
|
44
|
-
style,
|
|
45
|
-
documents: [],
|
|
46
|
-
delivered: false,
|
|
47
|
-
note: `Still rendering after ${Math.round(err.timeoutMs / 1000)}s. The task is not lost — ` +
|
|
48
|
-
"say so rather than commissioning it again.",
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
export function writeDocument(opts) {
|
|
52
|
-
if (!opts.client && !opts.apiKey?.trim()) {
|
|
53
|
-
throw new Error("writeDocument needs a Moa apiKey; it will not read one from the process.");
|
|
54
|
-
}
|
|
55
|
-
const allowed = opts.formats?.length ? opts.formats : ALL_FORMATS;
|
|
56
|
-
const design = mergeDesign(DEFAULT_DESIGN, opts.design);
|
|
57
|
-
const maxChars = opts.maxChars ?? DEFAULT_MAX_CHARS;
|
|
58
|
-
const client = opts.client ??
|
|
59
|
-
createCommissionClient({
|
|
60
|
-
apiKey: opts.apiKey,
|
|
61
|
-
baseUrl: opts.baseUrl,
|
|
62
|
-
...(opts.fetch ? { fetch: opts.fetch } : {}),
|
|
63
|
-
});
|
|
64
|
-
return tool({
|
|
65
|
-
name: "write_document",
|
|
66
|
-
description: describe(allowed, design),
|
|
67
|
-
effect: "destructive",
|
|
68
|
-
args: [
|
|
69
|
-
{
|
|
70
|
-
name: "brief",
|
|
71
|
-
type: "text",
|
|
72
|
-
required: true,
|
|
73
|
-
maxLength: 8000,
|
|
74
|
-
description: "What to produce, who reads it, what it must cover and how long it runs. Write it " +
|
|
75
|
-
"as you would brief a person who knows the craft and nothing about this account.",
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
name: "format",
|
|
79
|
-
type: "enum",
|
|
80
|
-
required: true,
|
|
81
|
-
options: ["pdf", "pptx", "docx", "md"],
|
|
82
|
-
description: "pptx for anything presented, pdf for anything sent, docx for anything edited.",
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
name: "source",
|
|
86
|
-
type: "text",
|
|
87
|
-
required: false,
|
|
88
|
-
maxLength: 40000,
|
|
89
|
-
description: "The material it must build from — figures, findings, quotes, an outline — verbatim. " +
|
|
90
|
-
"Anything missing here the agent will either omit or invent.",
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
name: "design_notes",
|
|
94
|
-
type: "text",
|
|
95
|
-
required: false,
|
|
96
|
-
maxLength: 2000,
|
|
97
|
-
description: "How this one document departs from the house style, in plain words. Leave it empty " +
|
|
98
|
-
"unless the user asked for something specific.",
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
name: "session_id",
|
|
102
|
-
type: "string",
|
|
103
|
-
required: false,
|
|
104
|
-
description: "Continue an earlier write_document instead of starting over — take it from that " +
|
|
105
|
-
"result's sessionId. Use it to revise a document or to recast one into another format.",
|
|
106
|
-
},
|
|
107
|
-
],
|
|
108
|
-
run: async ({ brief, format, source, design_notes, session_id }) => {
|
|
109
|
-
if (!allowed.includes(format)) {
|
|
110
|
-
throw new Error(`write_document was asked for .${format}, which this deployment does not produce ` +
|
|
111
|
-
`(${allowed.map((f) => `.${f}`).join(", ")}).`);
|
|
112
|
-
}
|
|
113
|
-
const id = await client.create({
|
|
114
|
-
task: brief,
|
|
115
|
-
context: source,
|
|
116
|
-
design: renderDesign(design, format, design_notes),
|
|
117
|
-
artifacts: [format],
|
|
118
|
-
...(session_id ? { sessionId: session_id } : {}),
|
|
119
|
-
});
|
|
120
|
-
try {
|
|
121
|
-
const result = await pollUntilDone(client, id, {
|
|
122
|
-
timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
123
|
-
settleMs: DEFAULT_SETTLE_MS,
|
|
124
|
-
expectArtifacts: true,
|
|
125
|
-
...(opts.signal ? { signal: opts.signal } : {}),
|
|
126
|
-
});
|
|
127
|
-
if (result.status === "failed") {
|
|
128
|
-
throw new Error(`write_document: Moa task ${id} failed: ${result.error ?? "no reason given"}`);
|
|
129
|
-
}
|
|
130
|
-
return delivered(result, design.name, maxChars, opts.includeData === true);
|
|
131
|
-
}
|
|
132
|
-
catch (err) {
|
|
133
|
-
if (err instanceof CommissionAbortedError && opts.cancelOnAbort !== false) {
|
|
134
|
-
await client.cancel?.(id).catch(() => undefined);
|
|
135
|
-
}
|
|
136
|
-
if (err instanceof CommissionTimeoutError)
|
|
137
|
-
return stillRendering(id, design.name, err);
|
|
138
|
-
throw err;
|
|
139
|
-
}
|
|
140
|
-
},
|
|
141
|
-
});
|
|
142
|
-
}
|
|
1
|
+
import{tool as f}from"../../tool/index.js";import{CommissionAbortedError as l,CommissionTimeoutError as h,createCommissionClient as g,pollUntilDone as p}from"./commission.js";import{DEFAULT_DESIGN as w,mergeDesign as y,renderDesign as _}from"./design.js";const x=["pdf","pptx","docx","md"],b=6e5,M=2e3,U=4e3;function v(e,i){return`Commissions a document agent to write and render a finished file \u2014 ${e.map(t=>`.${t}`).join(", ")} \u2014 and hands back where to download it. Use it when the answer has to be a document somebody opens: a report, a deck, a proposal, a one-pager. Not for text you can simply write in your reply. It runs for minutes and it costs money, so brief it once and brief it fully \u2014 the agent cannot see this conversation, so every fact, figure and quote it must use goes in \`source\`. Design is already settled by ${i.name??"the house style"}; use \`design_notes\` only for what this one document must do differently.`}function E(e,i,t,d){const r=(e.artifacts??[]).map(o=>({name:o.name,format:o.format,url:o.url,mime:o.mime,sizeBytes:o.sizeBytes,...d&&o.data?{data:o.data}:{}}));return{taskId:e.id,sessionId:e.sessionId,status:"done",style:i,documents:r,delivered:r.length>0,writeUp:e.output?.slice(0,t),truncated:(e.output?.length??0)>t,turns:e.numTurns,costMicroUsd:e.costUsd===void 0?void 0:Math.round(e.costUsd*1e6)}}function T(e,i,t){return{taskId:e,status:"running",style:i,documents:[],delivered:!1,note:`Still rendering after ${Math.round(t.timeoutMs/1e3)}s. The task is not lost \u2014 say so rather than commissioning it again.`}}function L(e){if(!e.client&&!e.apiKey?.trim())throw new Error("writeDocument needs a Moa apiKey; it will not read one from the process.");const i=e.formats?.length?e.formats:x,t=y(w,e.design),d=e.maxChars??U,r=e.client??g({apiKey:e.apiKey,baseUrl:e.baseUrl,...e.fetch?{fetch:e.fetch}:{}});return f({name:"write_document",description:v(i,t),effect:"destructive",args:[{name:"brief",type:"text",required:!0,maxLength:8e3,description:"What to produce, who reads it, what it must cover and how long it runs. Write it as you would brief a person who knows the craft and nothing about this account."},{name:"format",type:"enum",required:!0,options:["pdf","pptx","docx","md"],description:"pptx for anything presented, pdf for anything sent, docx for anything edited."},{name:"source",type:"text",required:!1,maxLength:4e4,description:"The material it must build from \u2014 figures, findings, quotes, an outline \u2014 verbatim. Anything missing here the agent will either omit or invent."},{name:"design_notes",type:"text",required:!1,maxLength:2e3,description:"How this one document departs from the house style, in plain words. Leave it empty unless the user asked for something specific."},{name:"session_id",type:"string",required:!1,description:"Continue an earlier write_document instead of starting over \u2014 take it from that result's sessionId. Use it to revise a document or to recast one into another format."}],run:async({brief:o,format:s,source:u,design_notes:m,session_id:c})=>{if(!i.includes(s))throw new Error(`write_document was asked for .${s}, which this deployment does not produce (${i.map(n=>`.${n}`).join(", ")}).`);const a=await r.create({task:o,context:u,design:_(t,s,m),artifacts:[s],...c?{sessionId:c}:{}});try{const n=await p(r,a,{timeoutMs:e.timeoutMs??b,settleMs:M,expectArtifacts:!0,...e.signal?{signal:e.signal}:{}});if(n.status==="failed")throw new Error(`write_document: Moa task ${a} failed: ${n.error??"no reason given"}`);return E(n,t.name,d,e.includeData===!0)}catch(n){if(n instanceof l&&e.cancelOnAbort!==!1&&await r.cancel?.(a).catch(()=>{}),n instanceof h)return T(a,t.name,n);throw n}}})}export{L as writeDocument};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ArgSpec } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* The arguments a handler is given: declared defaults filled in, and every
|
|
4
|
+
* declared value at its declared type.
|
|
5
|
+
*
|
|
6
|
+
* Validation runs before this and on the raw arguments, so what is required
|
|
7
|
+
* stays required — a `default` fills a gap, it does not satisfy a demand.
|
|
8
|
+
* Anything the tool did not declare is passed through untouched: the model may
|
|
9
|
+
* have sent it, and dropping it would be this package deciding it knows better.
|
|
10
|
+
*/
|
|
11
|
+
export declare function readyArgs(specs: readonly ArgSpec[], args: Record<string, unknown>): Record<string, unknown>;
|
package/dist/bus/args.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{isMissing as s,toBoolean as c,toNumber as f}from"@spendgraph/sdk";function i(e,n){switch(e.type){case"number":return f(n);case"boolean":return c(n);case"list":return Array.isArray(n)?n.map(String):n;case"json":return n;default:return String(n)}}function a(e,n){const t={...n};for(const r of e){const o=t[r.name];if(s(o)){if(r.default===void 0)continue;t[r.name]=i(r,r.default);continue}t[r.name]=i(r,o)}return t}export{a as readyArgs};
|
package/dist/bus/bus.js
CHANGED
|
@@ -1,49 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { invokeTool } from "./invoke.js";
|
|
3
|
-
import { warnOnOverlap } from "./overlap.js";
|
|
4
|
-
import { selectTools } from "./select.js";
|
|
5
|
-
import { toStep } from "./step.js";
|
|
6
|
-
import { newTrace } from "./trace.js";
|
|
7
|
-
export function toolbus(tools, opts = {}) {
|
|
8
|
-
const now = opts.now ?? (() => Date.now());
|
|
9
|
-
const ceiling = Math.max(1, opts.limit ?? 6);
|
|
10
|
-
const byName = new Map();
|
|
11
|
-
for (const t of tools) {
|
|
12
|
-
if (byName.has(t.name))
|
|
13
|
-
throw new Error(`Two tools are called "${t.name}".`);
|
|
14
|
-
byName.set(t.name, t);
|
|
15
|
-
}
|
|
16
|
-
const calls = new Map();
|
|
17
|
-
if (opts.warnOnOverlap ?? true)
|
|
18
|
-
warnOnOverlap([...byName.values()]);
|
|
19
|
-
const declare = (t) => ({
|
|
20
|
-
name: t.name,
|
|
21
|
-
description: t.description,
|
|
22
|
-
args: t.args,
|
|
23
|
-
...(t.effect ? { effect: t.effect } : {}),
|
|
24
|
-
});
|
|
25
|
-
const all = () => [...byName.values()].map(declare);
|
|
26
|
-
const bus = {
|
|
27
|
-
all: () => [...byName.values()],
|
|
28
|
-
missing: (names) => names.filter((n) => !byName.has(n)),
|
|
29
|
-
get: (name) => byName.get(name),
|
|
30
|
-
effectOf: (name) => byName.get(name)?.effect,
|
|
31
|
-
destructive: () => [...byName.values()].filter((t) => t.effect === "destructive").map((t) => t.name),
|
|
32
|
-
unannotated: () => [...byName.values()].filter((t) => t.effect === undefined).map((t) => t.name),
|
|
33
|
-
declarations: (names) => names
|
|
34
|
-
? names
|
|
35
|
-
.map((n) => byName.get(n))
|
|
36
|
-
.filter(Boolean)
|
|
37
|
-
.map((t) => declare(t))
|
|
38
|
-
: all(),
|
|
39
|
-
anthropic: (decls) => toAnthropic(decls ?? all()),
|
|
40
|
-
openai: (decls) => toOpenAI(decls ?? all()),
|
|
41
|
-
markdown: (decls, heading) => toMarkdown(decls ?? all(), heading),
|
|
42
|
-
select: (query = "", limit) => selectTools([...byName.values()], calls, declare, query, Math.max(1, limit ?? ceiling)),
|
|
43
|
-
invoke: (name, args) => invokeTool(byName, calls, now, name, args),
|
|
44
|
-
usage: () => Object.fromEntries(calls),
|
|
45
|
-
step: (result, index) => toStep(result, index),
|
|
46
|
-
trace: (query = "", limit) => newTrace(bus.select(query, limit), (name, args) => bus.invoke(name, args)),
|
|
47
|
-
};
|
|
48
|
-
return bus;
|
|
49
|
-
}
|
|
1
|
+
import{toAnthropic as w,toMarkdown as d,toOpenAI as g}from"../wire/index.js";import{invokeTool as O}from"./invoke.js";import{warnOnOverlap as h}from"./overlap.js";import{scorerFor as k}from"./score.js";import{selectTools as b}from"./select.js";import{toStep as T}from"./step.js";import{ceilingFor as l}from"./terms.js";import{newTrace as M}from"./trace.js";function I(c,a={}){const f=a.now??(()=>Date.now()),m=l(a.limit,6),t=new Map;for(const e of c){if(t.has(e.name))throw new Error(`Two tools are called "${e.name}".`);t.set(e.name,e)}const n=new Map;(a.warnOnOverlap??!0)&&h([...t.values()]);const p=k([...t.values()]),s=e=>({name:e.name,description:e.description,args:e.args,...e.aliases?.length?{aliases:e.aliases}:{},...e.effect?{effect:e.effect}:{}}),r=()=>[...t.values()].map(s),i={all:()=>[...t.values()],missing:e=>e.filter(o=>!t.has(o)),get:e=>t.get(e),effectOf:e=>t.get(e)?.effect,destructive:()=>[...t.values()].filter(e=>e.effect==="destructive").map(e=>e.name),unannotated:()=>[...t.values()].filter(e=>e.effect===void 0).map(e=>e.name),declarations:e=>e?e.map(o=>t.get(o)).filter(Boolean).map(o=>s(o)):r(),anthropic:e=>w(e??r()),openai:e=>g(e??r()),markdown:(e,o)=>d(e??r(),o),select:(e="",o)=>b([...t.values()],n,s,p,e,l(o,m)),invoke:(e,o)=>O(t,n,f,e,o),usage:()=>Object.fromEntries(n),step:(e,o)=>T(e,o),trace:(e="",o)=>M(i.select(e,o),(u,v)=>i.invoke(u,v))};return i}export{I as toolbus};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ToolBus } from "./bus.js";
|
|
2
|
+
import type { ToolBusOptions } from "./options.js";
|
|
3
|
+
export interface BusMember {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
bus: ToolBus;
|
|
7
|
+
}
|
|
8
|
+
export declare function busgroup(members: BusMember[], opts?: ToolBusOptions): ToolBus;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{toAnthropic as O,toMarkdown as I,toOpenAI as $}from"../wire/index.js";import{scorerFor as k}from"./score.js";import{toStep as A}from"./step.js";import{ceilingFor as M,termsIn as T}from"./terms.js";import{newTrace as x}from"./trace.js";const B=6,v=3;function D(t){return{name:t.name,description:t.description,args:[]}}function F(t){const s=new Map;for(const e of t)for(const o of e.bus.all()){const c=s.get(o.name);if(c)throw new Error(`Two buses offer a tool called "${o.name}": "${c.name}" and "${e.name}". A name has to say which one runs.`);s.set(o.name,e)}return s}function S(t){return t.flatMap(s=>{const e=s.bus.all().filter(o=>o.pinned).map(o=>o.name);return e.length>0?s.bus.declarations(e):[]})}function C(t){const s=[],e=new Set;let o=t;return{chosen:s,holds:c=>e.has(c),full:()=>s.length>=o,room:()=>t-s.length,narrowTo:c=>{o=Math.min(o,c)},take(c){e.has(c.name)||s.length>=o||(e.add(c.name),s.push(c))}}}function L(t,s,e){const o=t.flat(),c=k(o),f=o.map(l=>({declaration:l,n:c(l,s)})).sort((l,u)=>u.n-l.n);if(f.length===0||f[0].n===0)return!1;f.length>1&&f[0].n>=f[1].n*2&&e.narrowTo(e.chosen.length+v);for(const{declaration:l}of f){if(e.full())break;e.take(l)}return!0}function N(t,s){for(let e=0;!s.full();e++){let o=!1;for(const c of t){const f=c[e];if(f!==void 0&&(o=!0,s.take(f),s.full()))break}if(!o)break}}function z(t,s={}){const e=new Set;for(const n of t){if(e.has(n.name))throw new Error(`Two buses are called "${n.name}".`);e.add(n.name)}const o=F(t),c=S(t),f=new Map(t.map(n=>[n,D(n)])),m=k([...f.values()]),l=M(s.limit,B),u=n=>o.get(n),b=()=>t.flatMap(n=>n.bus.all()),p=()=>t.flatMap(n=>n.bus.declarations()),y=n=>t.map(a=>({member:a,n:m(f.get(a),n)})).sort((a,r)=>r.n-a.n),g={all:b,missing:n=>n.filter(a=>!o.has(a)),get:n=>u(n)?.bus.get(n),effectOf:n=>u(n)?.bus.effectOf(n),destructive:()=>t.flatMap(n=>n.bus.destructive()),unannotated:()=>t.flatMap(n=>n.bus.unannotated()),declarations:n=>n?t.flatMap(a=>{const r=n.filter(i=>o.get(i)===a);return r.length>0?a.bus.declarations(r):[]}):p(),anthropic:n=>O(n??p()),openai:n=>$(n??p()),markdown:(n,a)=>I(n??p(),a),select:(n="",a)=>{const r=C(M(a,l));for(const h of c)r.take(h);const i=r.room();if(i<=0)return r.chosen;const d=y(T(n)),w=d.map(({member:h})=>h.bus.select(n,i).filter(E=>!r.holds(E.name)));if(d.length>1&&d[0].n>0&&d[0].n>=d[1].n*2&&w[0].length>0){r.narrowTo(r.chosen.length+v);for(const h of w[0])r.take(h);return r.chosen}return L(w,T(n),r)||N(w,r),r.chosen},invoke:async(n,a)=>{const r=u(n);return r?r.bus.invoke(n,a):{name:n,args:a,status:"failed",output:"",error:`No tool called "${n}". Offered: ${[...o.keys()].join(", ")}`,latencyMs:0}},usage:()=>Object.assign({},...t.map(n=>n.bus.usage())),step:(n,a)=>A(n,a),trace:(n="",a)=>x(g.select(n,a),(r,i)=>g.invoke(r,i))};return g}export{z as busgroup};
|
package/dist/bus/index.d.ts
CHANGED
package/dist/bus/index.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { newTrace } from "./trace.js";
|
|
1
|
+
import{toolbus as e}from"./bus.js";import{busgroup as t}from"./group.js";import{newTrace as m}from"./trace.js";export{t as busgroup,m as newTrace,e as toolbus};
|
package/dist/bus/invoke.js
CHANGED
|
@@ -1,43 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
export async function invokeTool(byName, calls, now, name, args) {
|
|
3
|
-
const started = now();
|
|
4
|
-
const found = byName.get(name);
|
|
5
|
-
const base = { name, args, latencyMs: 0 };
|
|
6
|
-
if (!found) {
|
|
7
|
-
return {
|
|
8
|
-
...base,
|
|
9
|
-
status: "failed",
|
|
10
|
-
output: "",
|
|
11
|
-
error: `No tool called "${name}". Offered: ${[...byName.keys()].join(", ")}`,
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
const errors = validateFields(args, found.args);
|
|
15
|
-
if (errors.length > 0) {
|
|
16
|
-
return {
|
|
17
|
-
...base,
|
|
18
|
-
status: "failed",
|
|
19
|
-
output: "",
|
|
20
|
-
error: new FieldValidationError(errors).message,
|
|
21
|
-
latencyMs: now() - started,
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
calls.set(name, (calls.get(name) ?? 0) + 1);
|
|
25
|
-
try {
|
|
26
|
-
const value = await found.run(args);
|
|
27
|
-
return {
|
|
28
|
-
...base,
|
|
29
|
-
status: "completed",
|
|
30
|
-
output: typeof value === "string" ? value : JSON.stringify(value),
|
|
31
|
-
latencyMs: now() - started,
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
catch (err) {
|
|
35
|
-
return {
|
|
36
|
-
...base,
|
|
37
|
-
status: "failed",
|
|
38
|
-
output: "",
|
|
39
|
-
error: err instanceof Error ? err.message : "Tool failed.",
|
|
40
|
-
latencyMs: now() - started,
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
}
|
|
1
|
+
import{FieldValidationError as d,validateFields as f}from"@spendgraph/sdk";import{readyArgs as p}from"./args.js";import{reasonFor as y,render as g}from"./output.js";async function F(n,i,r,t,s){const a=r(),e=n.get(t),o={name:t,args:s,latencyMs:0};if(!e)return{...o,status:"failed",output:"",error:`No tool called "${t}". Offered: ${[...n.keys()].join(", ")}`};const l=f(s,e.args);if(l.length>0)return{...o,status:"failed",output:"",error:new d(l).message,latencyMs:r()-a};i.set(t,(i.get(t)??0)+1);const c=p(e.args,s);try{const u=await e.run(c);return{...o,status:"completed",output:g(u),latencyMs:r()-a}}catch(u){return{...o,status:"failed",output:"",error:y(u),latencyMs:r()-a}}}export{F as invokeTool};
|
package/dist/bus/options.js
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a handler returned, as the string a result carries.
|
|
3
|
+
*
|
|
4
|
+
* A string is itself; anything else is JSON. `graph`'s `stringify` describes
|
|
5
|
+
* this as "the same convention as a tool result" — it was not, and this is the
|
|
6
|
+
* side that had drifted.
|
|
7
|
+
*
|
|
8
|
+
* `JSON.stringify` returns `undefined` for a function or an undefined value, so
|
|
9
|
+
* a completed result carried `output: undefined` where its own type says
|
|
10
|
+
* `string`, and the model was handed nothing while being told it was handed
|
|
11
|
+
* something. And it *throws* on a circular object, a `BigInt`, or anything whose
|
|
12
|
+
* `toJSON` throws — a circular object being any value with a parent backlink.
|
|
13
|
+
*
|
|
14
|
+
* What comes back when a value cannot be written is a note saying so, never
|
|
15
|
+
* `""`: an empty output reads as a tool that produced nothing, and this is a
|
|
16
|
+
* tool that produced something unwritable.
|
|
17
|
+
*/
|
|
18
|
+
export declare function render(value: unknown): string;
|
|
19
|
+
/**
|
|
20
|
+
* Why a handler failed, from whatever it threw.
|
|
21
|
+
*
|
|
22
|
+
* Anything can be thrown, and a string is thrown often enough. Reading only
|
|
23
|
+
* `Error.message` turned `throw "upstream refused"` into "Tool failed.", which
|
|
24
|
+
* discards the one thing the tool said about it.
|
|
25
|
+
*/
|
|
26
|
+
export declare function reasonFor(err: unknown): string;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function r(n){if(n==null)return"";if(typeof n=="string")return n;try{const t=JSON.stringify(n);return t===void 0?`[not recordable: ${typeof n}]`:t}catch(t){return`[not recordable: ${(t instanceof Error?t.message:String(t)).split(`
|
|
2
|
+
`)[0]}]`}}function o(n){if(n instanceof Error&&n.message.trim())return n.message;if(typeof n=="string"&&n.trim())return n;const t=r(n);return t&&t!=="{}"?`Tool failed: ${t}`:"Tool failed."}export{o as reasonFor,r as render};
|
package/dist/bus/overlap.js
CHANGED
|
@@ -1,31 +1 @@
|
|
|
1
|
-
function
|
|
2
|
-
return new Set(tool.description
|
|
3
|
-
.toLowerCase()
|
|
4
|
-
.split(/[^a-z0-9]+/)
|
|
5
|
-
.filter((w) => w.length > 3));
|
|
6
|
-
}
|
|
7
|
-
export function warnOnOverlap(tools) {
|
|
8
|
-
const sets = tools.map(words);
|
|
9
|
-
const named = [];
|
|
10
|
-
let pairs = 0;
|
|
11
|
-
for (let i = 0; i < tools.length; i++) {
|
|
12
|
-
for (let j = i + 1; j < tools.length; j++) {
|
|
13
|
-
const a = sets[i];
|
|
14
|
-
const b = sets[j];
|
|
15
|
-
if (a.size === 0 || b.size === 0)
|
|
16
|
-
continue;
|
|
17
|
-
const shared = [...a].filter((w) => b.has(w)).length;
|
|
18
|
-
if (shared / Math.min(a.size, b.size) < 0.8)
|
|
19
|
-
continue;
|
|
20
|
-
pairs += 1;
|
|
21
|
-
if (named.length < 3)
|
|
22
|
-
named.push(`"${tools[i].name}" and "${tools[j].name}"`);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
if (pairs === 0)
|
|
26
|
-
return;
|
|
27
|
-
const rest = pairs - named.length;
|
|
28
|
-
console.warn(`[toolbus] ${pairs} ${pairs === 1 ? "pair of tools describes" : "pairs of tools describe"} ` +
|
|
29
|
-
`themselves almost identically: ${named.join(", ")}${rest > 0 ? `, and ${rest} more` : ""}. ` +
|
|
30
|
-
`The model will struggle to choose between them — give them distinct descriptions, or merge them.`);
|
|
31
|
-
}
|
|
1
|
+
import{termsIn as h}from"./terms.js";function m(e){return new Set(h(e.description).filter(i=>i.length>3))}function g(e){const i=e.map(m),s=[];let t=0;for(let n=0;n<e.length;n++)for(let r=n+1;r<e.length;r++){const o=i[n],l=i[r];o.size===0||l.size===0||[...o].filter(c=>l.has(c)).length/Math.min(o.size,l.size)<.8||(t+=1,s.length<3&&s.push(`"${e[n].name}" and "${e[r].name}"`))}if(t===0)return;const a=t-s.length;console.warn(`[toolbus] ${t} ${t===1?"pair of tools describes":"pairs of tools describe"} themselves almost identically: ${s.join(", ")}${a>0?`, and ${a} more`:""}. The model will struggle to choose between them \u2014 give them distinct descriptions, or merge them.`)}export{g as warnOnOverlap};
|
package/dist/bus/score.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
export interface Describable {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
args: readonly {
|
|
5
|
+
name: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
}[];
|
|
8
|
+
aliases?: readonly string[];
|
|
9
|
+
}
|
|
10
|
+
export type Scorer = (subject: Describable, terms: string[]) => number;
|
|
11
|
+
export declare function scorerFor(corpus: readonly Describable[]): Scorer;
|
package/dist/bus/score.js
CHANGED
|
@@ -1,12 +1 @@
|
|
|
1
|
-
|
|
2
|
-
const name = tool.name.toLowerCase().replace(/_/g, " ");
|
|
3
|
-
const body = `${tool.description} ${tool.args.map((a) => `${a.name} ${a.description ?? ""}`).join(" ")}`.toLowerCase();
|
|
4
|
-
let n = 0;
|
|
5
|
-
for (const term of terms) {
|
|
6
|
-
if (name.includes(term))
|
|
7
|
-
n += 3;
|
|
8
|
-
else if (body.includes(term))
|
|
9
|
-
n += 1;
|
|
10
|
-
}
|
|
11
|
-
return n;
|
|
12
|
-
}
|
|
1
|
+
import{termsIn as l}from"./terms.js";const h=3,m=2,w=1,d=new WeakMap,a=n=>new Set(l(n));function g(n){const t=d.get(n);if(t)return t;const r={name:a(n.name.replace(/_/g," ")),aliases:a((n.aliases??[]).join(" ")),body:a(`${n.description} ${n.args.map(o=>`${o.name} ${o.description??""}`).join(" ")}`)};return d.set(n,r),r}function p(n,t){return n.name.has(t)?h:n.aliases.has(t)?m:n.body.has(t)?w:0}function I(n){const t=new Map;for(const o of n){const e=g(o);for(const s of new Set([...e.name,...e.aliases,...e.body]))t.set(s,(t.get(s)??0)+1)}const r=o=>Math.log((n.length+1)/((t.get(o)??0)+.5));return(o,e)=>{const s=g(o);let i=0;for(const c of e){const f=p(s,c);f>0&&(i+=f*r(c))}return i}}export{I as scorerFor};
|
package/dist/bus/select.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Tool, ToolDeclaration } from "../types/index.js";
|
|
2
|
+
import type { Scorer } from "./score.js";
|
|
2
3
|
/**
|
|
3
4
|
* The working set for one request: pinned, then what the query matches, then
|
|
4
5
|
* what actually gets used.
|
|
@@ -15,4 +16,4 @@ import type { Tool, ToolDeclaration } from "../types/index.js";
|
|
|
15
16
|
* match needs no company; a flat spread means the ranking does not know, and a
|
|
16
17
|
* wider net is worth the distraction.
|
|
17
18
|
*/
|
|
18
|
-
export declare function selectTools(tools: Tool<never>[], calls: Map<string, number>, declare: (t: Tool<never>) => ToolDeclaration, query: string, ceiling: number): ToolDeclaration[];
|
|
19
|
+
export declare function selectTools(tools: Tool<never>[], calls: Map<string, number>, declare: (t: Tool<never>) => ToolDeclaration, scoreOf: Scorer, query: string, ceiling: number): ToolDeclaration[];
|