@spendgraph/tools 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/agents/index.d.ts +1 -0
- package/dist/agents/index.js +1 -0
- package/dist/{builtin/write-document/write-document.d.ts → agents/moa/index.d.ts} +7 -2
- package/dist/agents/moa/index.js +1 -0
- package/dist/{builtin/write-document/commission.d.ts → agents/moa/moa.d.ts} +15 -15
- package/dist/agents/moa/moa.js +13 -0
- package/dist/{builtin/write-document → agents/moa}/types.d.ts +9 -9
- package/dist/builtin/calculate/calculate.d.ts +2 -0
- package/dist/builtin/calculate/calculate.js +1 -1
- package/dist/builtin/calculate/index.d.ts +2 -1
- package/dist/builtin/calculate/index.js +1 -1
- package/dist/builtin/code-exec/code-exec.d.ts +14 -0
- package/dist/builtin/date/date.d.ts +1 -1
- package/dist/builtin/date/date.js +1 -1
- package/dist/builtin/date/index.d.ts +1 -0
- package/dist/builtin/date/index.js +1 -1
- package/dist/builtin/date/shift.d.ts +1 -1
- package/dist/builtin/date/shift.js +1 -1
- package/dist/builtin/date/words.d.ts +13 -0
- package/dist/builtin/date/words.js +1 -0
- package/dist/builtin/decline/decline.d.ts +49 -0
- package/dist/builtin/decline/decline.js +1 -0
- package/dist/builtin/decline/index.d.ts +1 -0
- package/dist/builtin/decline/index.js +1 -0
- package/dist/builtin/escalate/escalate.d.ts +19 -0
- package/dist/builtin/escalate/escalate.js +1 -1
- package/dist/builtin/escalate/index.d.ts +1 -1
- package/dist/builtin/escalate/index.js +1 -1
- package/dist/builtin/extract/extract.d.ts +25 -0
- package/dist/builtin/extract/extract.js +1 -0
- package/dist/builtin/extract/index.d.ts +3 -0
- package/dist/builtin/extract/index.js +1 -0
- package/dist/builtin/extract/kinds.d.ts +6 -0
- package/dist/builtin/extract/kinds.js +1 -0
- package/dist/builtin/extract/readers.d.ts +7 -0
- package/dist/builtin/extract/readers.js +9 -0
- package/dist/builtin/finish/finish.d.ts +4 -0
- package/dist/builtin/finish/finish.js +1 -1
- package/dist/builtin/http/allow.d.ts +2 -0
- package/dist/builtin/http/allow.js +1 -1
- package/dist/builtin/http/hops.d.ts +10 -0
- package/dist/builtin/http/hops.js +1 -0
- package/dist/builtin/http/http.d.ts +9 -1
- package/dist/builtin/http/http.js +1 -1
- package/dist/builtin/http/index.d.ts +2 -0
- package/dist/builtin/http/index.js +1 -1
- package/dist/builtin/http/retry-after.d.ts +14 -0
- package/dist/builtin/http/retry-after.js +1 -0
- package/dist/builtin/index.d.ts +10 -6
- package/dist/builtin/index.js +1 -1
- package/dist/builtin/json/index.d.ts +1 -0
- package/dist/builtin/json/index.js +1 -1
- package/dist/builtin/json/json.js +1 -1
- package/dist/builtin/json/parse.d.ts +15 -0
- package/dist/builtin/json/parse.js +1 -0
- package/dist/builtin/notes/index.d.ts +1 -0
- package/dist/builtin/notes/index.js +1 -0
- package/dist/builtin/notes/notes.d.ts +45 -0
- package/dist/builtin/notes/notes.js +2 -0
- package/dist/builtin/openalex/api.d.ts +22 -0
- package/dist/builtin/openalex/api.js +1 -0
- package/dist/builtin/openalex/index.d.ts +4 -0
- package/dist/builtin/openalex/index.js +1 -0
- package/dist/builtin/openalex/openalex.d.ts +23 -0
- package/dist/builtin/openalex/openalex.js +1 -0
- package/dist/builtin/openalex/render.d.ts +2 -0
- package/dist/builtin/openalex/render.js +4 -0
- package/dist/builtin/polite.d.ts +14 -0
- package/dist/builtin/polite.js +1 -0
- package/dist/builtin/publish/index.d.ts +1 -0
- package/dist/builtin/publish/index.js +1 -0
- package/dist/builtin/publish/publish.d.ts +46 -0
- package/dist/builtin/publish/publish.js +1 -0
- package/dist/builtin/web-search/web-search.d.ts +10 -35
- package/dist/builtin/web-search/web-search.js +1 -1
- package/dist/builtin/wikidata/api.d.ts +40 -0
- package/dist/builtin/wikidata/api.js +1 -0
- package/dist/builtin/wikidata/index.d.ts +5 -0
- package/dist/builtin/wikidata/index.js +1 -0
- package/dist/builtin/wikidata/render.d.ts +13 -0
- package/dist/builtin/wikidata/render.js +2 -0
- package/dist/builtin/wikidata/wikidata.d.ts +21 -0
- package/dist/builtin/wikidata/wikidata.js +1 -0
- package/dist/bus/bus.d.ts +23 -1
- package/dist/bus/bus.js +1 -1
- package/dist/bus/group.d.ts +14 -1
- package/dist/bus/group.js +1 -1
- package/dist/bus/index.d.ts +4 -1
- package/dist/bus/index.js +1 -1
- package/dist/bus/invoke.d.ts +1 -1
- package/dist/bus/invoke.js +3 -1
- package/dist/bus/namespace.d.ts +21 -0
- package/dist/bus/namespace.js +1 -0
- package/dist/bus/once.d.ts +32 -0
- package/dist/bus/once.js +1 -0
- package/dist/bus/only.d.ts +29 -0
- package/dist/bus/only.js +0 -0
- package/dist/bus/options.d.ts +31 -0
- package/dist/bus/select.d.ts +0 -16
- package/dist/bus/select.js +1 -1
- package/dist/bus/spent.d.ts +16 -0
- package/dist/bus/spent.js +1 -0
- package/dist/bus/trace.d.ts +14 -1
- package/dist/bus/trace.js +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.js +1 -1
- package/dist/internals.d.ts +8 -1
- package/dist/internals.js +1 -1
- package/dist/memories/client.d.ts +91 -0
- package/dist/memories/client.js +0 -0
- package/dist/memories/context.d.ts +5 -0
- package/dist/memories/context.js +1 -0
- package/dist/{builtin → memories}/deep-recall/deep-recall.d.ts +3 -0
- package/dist/memories/deep-recall/deep-recall.js +1 -0
- package/dist/memories/explore/explore.d.ts +18 -0
- package/dist/memories/explore/explore.js +7 -0
- package/dist/memories/explore/subgraph.d.ts +12 -0
- package/dist/memories/explore/subgraph.js +3 -0
- package/dist/memories/index.d.ts +30 -0
- package/dist/memories/index.js +1 -0
- package/dist/memories/observe/observe.d.ts +7 -0
- package/dist/memories/observe/observe.js +1 -0
- package/dist/memories/recall/recall.d.ts +42 -0
- package/dist/memories/recall/recall.js +1 -0
- package/dist/memories/recall/render.d.ts +5 -0
- package/dist/memories/recall/render.js +3 -0
- package/dist/memories/remember/remember.d.ts +49 -0
- package/dist/memories/remember/remember.js +1 -0
- package/dist/search/index.d.ts +5 -0
- package/dist/search/index.js +1 -0
- package/dist/search/retry.d.ts +6 -0
- package/dist/search/retry.js +1 -0
- package/dist/search/search.d.ts +63 -0
- package/dist/search/search.js +1 -0
- package/dist/search/searxng/index.d.ts +2 -0
- package/dist/search/searxng/index.js +1 -0
- package/dist/search/searxng/searxng.d.ts +14 -0
- package/dist/search/searxng/searxng.js +5 -0
- package/dist/search/searxng/text.d.ts +2 -0
- package/dist/search/searxng/text.js +3 -0
- package/dist/search/sonar/index.d.ts +2 -0
- package/dist/search/sonar/index.js +1 -0
- package/dist/search/sonar/sonar.d.ts +13 -0
- package/dist/search/sonar/sonar.js +1 -0
- package/dist/search/tavily/index.d.ts +4 -0
- package/dist/search/tavily/index.js +1 -0
- package/dist/search/tavily/reply.d.ts +22 -0
- package/dist/search/tavily/reply.js +5 -0
- package/dist/search/tavily/tavily.d.ts +14 -0
- package/dist/search/tavily/tavily.js +1 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +1 -0
- package/dist/testing/replay.d.ts +55 -0
- package/dist/testing/replay.js +1 -0
- package/dist/testing/store.d.ts +35 -0
- package/dist/testing/store.js +4 -0
- package/dist/tool/tool.d.ts +4 -1
- package/dist/tool/tool.js +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/result.d.ts +38 -0
- package/dist/types/tool.d.ts +61 -0
- package/docs/bogus.mdx +85 -0
- package/docs/builtins.mdx +156 -0
- package/docs/bus.mdx +90 -0
- package/docs/declaring.mdx +85 -0
- package/docs/effects.mdx +49 -0
- package/docs/overview.mdx +76 -0
- package/docs/selecting.mdx +79 -0
- package/docs/stored.mdx +83 -0
- package/docs/turns.mdx +57 -0
- package/package.json +22 -9
- package/dist/builtin/deep-recall/deep-recall.js +0 -1
- package/dist/builtin/write-document/commission.js +0 -13
- package/dist/builtin/write-document/index.d.ts +0 -6
- package/dist/builtin/write-document/index.js +0 -1
- package/dist/builtin/write-document/write-document.js +0 -1
- /package/dist/{builtin/write-document → agents/moa}/design.d.ts +0 -0
- /package/dist/{builtin/write-document → agents/moa}/design.js +0 -0
- /package/dist/{builtin/write-document → agents/moa}/http.d.ts +0 -0
- /package/dist/{builtin/write-document → agents/moa}/http.js +0 -0
- /package/dist/{builtin/write-document → agents/moa}/types.js +0 -0
- /package/dist/{builtin → memories}/deep-recall/index.d.ts +0 -0
- /package/dist/{builtin → memories}/deep-recall/index.js +0 -0
- /package/dist/{builtin/web-search → search/sonar}/reply.d.ts +0 -0
- /package/dist/{builtin/web-search → search/sonar}/reply.js +0 -0
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
|
-
- **
|
|
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) |
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { type ArtifactFormat, DEFAULT_DESIGN, type DesignGuide, MoaAbortedError, type MoaClient, MoaTimeoutError, mergeDesign, renderDesign, type WriteDocumentOptions, writeDocument, } from "./moa/index.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{DEFAULT_DESIGN as o,MoaAbortedError as t,MoaTimeoutError as D,mergeDesign as i,renderDesign as m,writeDocument as n}from"./moa/index.js";export{o as DEFAULT_DESIGN,t as MoaAbortedError,D as MoaTimeoutError,i as mergeDesign,m as renderDesign,n as writeDocument};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type CommissionClient } from "./commission.js";
|
|
2
1
|
import { type DesignGuide } from "./design.js";
|
|
3
2
|
import type { AbortSignalLike } from "./http.js";
|
|
3
|
+
import { type MoaClient } from "./moa.js";
|
|
4
4
|
import type { ArtifactFormat } from "./types.js";
|
|
5
5
|
export interface WriteDocumentOptions {
|
|
6
6
|
/** Moa key. Required, and read from here rather than the environment. */
|
|
@@ -25,7 +25,7 @@ export interface WriteDocumentOptions {
|
|
|
25
25
|
/** Longest write-up handed back. Default 4000 characters. */
|
|
26
26
|
maxChars?: number;
|
|
27
27
|
/** Injected for tests, and for anyone fronting Moa with their own transport. */
|
|
28
|
-
client?:
|
|
28
|
+
client?: MoaClient;
|
|
29
29
|
/** Injected for tests. */
|
|
30
30
|
fetch?: typeof fetch;
|
|
31
31
|
}
|
|
@@ -67,3 +67,8 @@ export declare function writeDocument(opts: WriteDocumentOptions): import("../..
|
|
|
67
67
|
readonly required: false;
|
|
68
68
|
readonly description: string;
|
|
69
69
|
}]>>;
|
|
70
|
+
export { DEFAULT_DESIGN, type DesignGuide, mergeDesign, renderDesign } from "./design.js";
|
|
71
|
+
export { MOA_BASE_URL, MoaApiError, type MoaClientConfig } from "./http.js";
|
|
72
|
+
export type { MoaClient, PollOptions } from "./moa.js";
|
|
73
|
+
export { createMoaClient, INTERACTIVE_TIMEOUT_MS, MoaAbortedError, MoaTimeoutError, pollUntilDone, } from "./moa.js";
|
|
74
|
+
export type { ArtifactFormat, MoaArtifact, MoaInput, MoaResult, MoaStatus, } from "./types.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{tool as f}from"../../tool/index.js";import{DEFAULT_DESIGN as l,mergeDesign as h,renderDesign as g}from"./design.js";import{createMoaClient as p,MoaAbortedError as w,MoaTimeoutError as y,pollUntilDone as M}from"./moa.js";const _=["pdf","pptx","docx","md"],E=6e5,x=2e3,T=4e3;function A(e,r){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 ${r.name??"the house style"}; use \`design_notes\` only for what this one document must do differently.`}function U(e,r,t,d){const i=(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:r,documents:i,delivered:i.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 b(e,r,t){return{taskId:e,status:"running",style:r,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 r=e.formats?.length?e.formats:_,t=h(l,e.design),d=e.maxChars??T,i=e.client??p({apiKey:e.apiKey,baseUrl:e.baseUrl,...e.fetch?{fetch:e.fetch}:{}});return f({name:"write_document",description:A(r,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(!r.includes(s))throw new Error(`write_document was asked for .${s}, which this deployment does not produce (${r.map(n=>`.${n}`).join(", ")}).`);const a=await i.create({task:o,context:u,design:g(t,s,m),artifacts:[s],...c?{sessionId:c}:{}});try{const n=await M(i,a,{timeoutMs:e.timeoutMs??E,settleMs:x,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 U(n,t.name,d,e.includeData===!0)}catch(n){if(n instanceof w&&e.cancelOnAbort!==!1&&await i.cancel?.(a).catch(()=>{}),n instanceof y)return b(a,t.name,n);throw n}}})}import{DEFAULT_DESIGN as $,mergeDesign as q,renderDesign as C}from"./design.js";import{MOA_BASE_URL as O,MoaApiError as R}from"./http.js";import{createMoaClient as N,INTERACTIVE_TIMEOUT_MS as B,MoaAbortedError as j,MoaTimeoutError as z,pollUntilDone as G}from"./moa.js";export{$ as DEFAULT_DESIGN,B as INTERACTIVE_TIMEOUT_MS,O as MOA_BASE_URL,j as MoaAbortedError,R as MoaApiError,z as MoaTimeoutError,N as createMoaClient,q as mergeDesign,G as pollUntilDone,C as renderDesign,L as writeDocument};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type AbortSignalLike, type MoaClientConfig } from "./http.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { MoaInput, MoaResult } from "./types.js";
|
|
3
3
|
/**
|
|
4
4
|
* Moa's task surface — fire, then await.
|
|
5
5
|
*
|
|
@@ -7,40 +7,40 @@ import type { CommissionInput, CommissionResult } from "./types.js";
|
|
|
7
7
|
* (or use {@link pollUntilDone}) to long-poll, so one call is awaited rather
|
|
8
8
|
* than a loop tight-polling. `cancel()` is optional so a test stub stays small.
|
|
9
9
|
*/
|
|
10
|
-
export interface
|
|
11
|
-
create(input:
|
|
10
|
+
export interface MoaClient {
|
|
11
|
+
create(input: MoaInput): Promise<string>;
|
|
12
12
|
get(id: string, opts?: {
|
|
13
13
|
waitMs?: number;
|
|
14
14
|
signal?: AbortSignalLike;
|
|
15
|
-
}): Promise<
|
|
15
|
+
}): Promise<MoaResult>;
|
|
16
16
|
cancel?(id: string): Promise<void>;
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
|
-
* HTTP {@link
|
|
19
|
+
* HTTP {@link MoaClient} over Moa's task API (`POST /tasks` → `GET /tasks/:id`).
|
|
20
20
|
*
|
|
21
21
|
* The request body carries the ask as prose — Moa's artifact pipeline is
|
|
22
22
|
* prompt-driven and `POST /tasks` strips unknown fields, so context, design and
|
|
23
23
|
* deliverables are blocks on the task rather than parameters beside it.
|
|
24
24
|
*/
|
|
25
|
-
export declare function
|
|
25
|
+
export declare function createMoaClient(config?: MoaClientConfig): MoaClient;
|
|
26
26
|
/** The sane ceiling for a chat-bound call. A rendered document wants minutes, not this. */
|
|
27
27
|
export declare const INTERACTIVE_TIMEOUT_MS = 30000;
|
|
28
28
|
/** Thrown by {@link pollUntilDone} when the caller's abort signal fires. */
|
|
29
|
-
export declare class
|
|
30
|
-
readonly
|
|
31
|
-
constructor(
|
|
29
|
+
export declare class MoaAbortedError extends Error {
|
|
30
|
+
readonly taskId: string;
|
|
31
|
+
constructor(taskId: string);
|
|
32
32
|
}
|
|
33
33
|
/** Thrown when the wait ran out. The task itself is still running server-side. */
|
|
34
|
-
export declare class
|
|
35
|
-
readonly
|
|
34
|
+
export declare class MoaTimeoutError extends Error {
|
|
35
|
+
readonly taskId: string;
|
|
36
36
|
readonly timeoutMs: number;
|
|
37
|
-
constructor(
|
|
37
|
+
constructor(taskId: string, timeoutMs: number);
|
|
38
38
|
}
|
|
39
39
|
export interface PollOptions {
|
|
40
40
|
timeoutMs?: number;
|
|
41
41
|
pollIntervalMs?: number;
|
|
42
42
|
/**
|
|
43
|
-
* Stops the wait and throws {@link
|
|
43
|
+
* Stops the wait and throws {@link MoaAbortedError}, cutting the held
|
|
44
44
|
* long-poll with it. The task keeps running — cancelling it is the caller's call.
|
|
45
45
|
*/
|
|
46
46
|
signal?: AbortSignalLike;
|
|
@@ -52,5 +52,5 @@ export interface PollOptions {
|
|
|
52
52
|
/** Keep settling while a done task carries no artifacts. Set it when files were asked for. */
|
|
53
53
|
expectArtifacts?: boolean;
|
|
54
54
|
}
|
|
55
|
-
/** Wait for a
|
|
56
|
-
export declare function pollUntilDone(client:
|
|
55
|
+
/** Wait for a Moa task to settle, long-polling rather than tight-polling. */
|
|
56
|
+
export declare function pollUntilDone(client: MoaClient, id: string, opts?: PollOptions): Promise<MoaResult>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import{createRequest as w,MoaApiError as M}from"./http.js";const h={queued:"pending",running:"running",succeeded:"done",failed:"failed",canceled:"failed"},T=["md","pdf","docx","pptx"],g=n=>{const t=n?.split(".").pop()?.toLowerCase();return t&&T.includes(t)?t:void 0};function E(n){if(!n?.length)return;const t=n.flatMap(e=>{const s=g(e.name);return!s||!(e.url||e.data)?[]:[{name:e.name??`document.${s}`,format:s,url:e.url,data:e.data,mime:e.contentType,sizeBytes:e.sizeBytes}]});return t.length?t:void 0}function I(n){return`
|
|
2
|
+
|
|
3
|
+
<deliverables>
|
|
4
|
+
Besides the text answer, save the finished deliverable into the artifacts/ directory of your working dir as: ${n.map(t=>`one .${t} file`).join(", ")}.
|
|
5
|
+
</deliverables>`}function x(n){let t=n.task;return n.context&&(t+=`
|
|
6
|
+
|
|
7
|
+
<context>
|
|
8
|
+
${n.context}
|
|
9
|
+
</context>`),n.design&&(t+=`
|
|
10
|
+
|
|
11
|
+
<design>
|
|
12
|
+
${n.design}
|
|
13
|
+
</design>`),n.artifacts?.length&&(t+=I(n.artifacts)),t}function p(n={}){const t=w(n);return{async create(e){const s=await t("/tasks",{method:"POST",body:{task:x(e),mode:"striker",...e.sessionId?{sessionId:e.sessionId}:{},...e.newSessionId?{newSessionId:e.newSessionId}:{}}});if(!s.id)throw new M("Moa did not return a task id",502);return s.id},async get(e,s){const c=s?.waitMs&&s.waitMs>0?`?waitMs=${Math.floor(s.waitMs)}`:"",r=await t(`/tasks/${encodeURIComponent(e)}${c}`,{signal:s?.signal}),a=r.status?h[r.status]:void 0;return{id:r.id??e,status:a??"running",output:r.result,error:r.error,costUsd:r.costUsd,numTurns:r.numTurns,sessionId:r.sessionId,artifacts:E(r.artifacts)}},async cancel(e){await t(`/tasks/${encodeURIComponent(e)}`,{method:"DELETE"})}}}const k=3e4,b=2e3,A=3,y=25e3;class f extends Error{taskId;constructor(t){super(`Moa task ${t} was cancelled by the caller.`),this.name="MoaAbortedError",this.taskId=t}}class S extends Error{taskId;timeoutMs;constructor(t,e){super(`Moa task ${t} did not complete within ${e/1e3}s. The task may still be running in the background.`),this.name="MoaTimeoutError",this.taskId=t,this.timeoutMs=e}}const m=(n,t)=>new Promise(e=>{const s=()=>e();t?.addEventListener("abort",s,{once:!0}),setTimeout(()=>{t?.removeEventListener("abort",s),e()},n)});async function L(n,t,e={}){const s=e.timeoutMs??k,c=e.pollIntervalMs??b,r=e.settleMs??0,a=e.signal,l=Date.now()+s;for(;;){if(a?.aborted)throw new f(t);const u=l-Date.now();if(u<=0)throw new S(t,s);let o;try{o=await n.get(t,{waitMs:Math.min(u,y),signal:a})}catch(d){throw a?.aborted?new f(t):d}if(o.status==="failed")return o;if(o.status==="done"){const d=i=>!i.output?.trim()||e.expectArtifacts===!0&&!i.artifacts?.length;for(let i=0;r>0&&i<A&&d(o);i++)await m(r),o=await n.get(t);return o}await m(Math.min(c,Math.max(0,l-Date.now())),a)}}export{k as INTERACTIVE_TIMEOUT_MS,f as MoaAbortedError,S as MoaTimeoutError,p as createMoaClient,L as pollUntilDone};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/** A document format Moa can leave behind in `artifacts/`. */
|
|
2
2
|
export type ArtifactFormat = "md" | "pdf" | "docx" | "pptx";
|
|
3
|
-
/** Where a
|
|
4
|
-
export type
|
|
3
|
+
/** Where a Moa task has got to. `pending` is queued, `failed` is settled. */
|
|
4
|
+
export type MoaStatus = "pending" | "running" | "done" | "failed";
|
|
5
5
|
/** One finished file, either downloadable or carried inline as base64. */
|
|
6
|
-
export interface
|
|
6
|
+
export interface MoaArtifact {
|
|
7
7
|
name: string;
|
|
8
8
|
format: ArtifactFormat;
|
|
9
9
|
url?: string;
|
|
@@ -11,8 +11,8 @@ export interface CommissionArtifact {
|
|
|
11
11
|
mime?: string;
|
|
12
12
|
sizeBytes?: number;
|
|
13
13
|
}
|
|
14
|
-
/** What to
|
|
15
|
-
export interface
|
|
14
|
+
/** What to hand Moa: the ask, the material it rests on, and the house style. */
|
|
15
|
+
export interface MoaInput {
|
|
16
16
|
task: string;
|
|
17
17
|
context?: string;
|
|
18
18
|
design?: string;
|
|
@@ -20,14 +20,14 @@ export interface CommissionInput {
|
|
|
20
20
|
sessionId?: string;
|
|
21
21
|
newSessionId?: string;
|
|
22
22
|
}
|
|
23
|
-
/** A
|
|
24
|
-
export interface
|
|
23
|
+
/** A Moa task as it stands right now. */
|
|
24
|
+
export interface MoaResult {
|
|
25
25
|
id: string;
|
|
26
|
-
status:
|
|
26
|
+
status: MoaStatus;
|
|
27
27
|
output?: string;
|
|
28
28
|
error?: string;
|
|
29
29
|
costUsd?: number;
|
|
30
30
|
numTurns?: number;
|
|
31
31
|
sessionId?: string;
|
|
32
|
-
artifacts?:
|
|
32
|
+
artifacts?: MoaArtifact[];
|
|
33
33
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{tool as
|
|
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 +1 @@
|
|
|
1
|
-
import{calculate as
|
|
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
|
|
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 +1 @@
|
|
|
1
|
-
import{dateMath as r}from"./date.js";import{between as
|
|
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
|
|
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
|
|
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
|
|
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 @@
|
|
|
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 +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(
|
|
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
|
|
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 +1 @@
|
|
|
1
|
-
import{checkUrl as
|
|
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};
|