standard-tool 0.0.1 → 0.0.2

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 CHANGED
@@ -1,5 +1,4 @@
1
- # standard-tool
2
- [![npm](https://img.shields.io/npm/v/standard-tool)](https://www.npmjs.com/package/standard-tool) [![CI](https://github.com/finom/standard-tool/actions/workflows/ci.yml/badge.svg)](https://github.com/finom/standard-tool/actions/workflows/ci.yml)
1
+ # standard-tool  [![npm](https://img.shields.io/npm/v/standard-tool)](https://www.npmjs.com/package/standard-tool) [![CI](https://github.com/finom/standard-tool/actions/workflows/ci.yml/badge.svg)](https://github.com/finom/standard-tool/actions/workflows/ci.yml)
3
2
 
4
3
  > A **standalone**, dependency-free convention for defining LLM tools — built on [Standard Schema](https://standardschema.dev) + [Standard JSON Schema](https://standardschema.dev/json-schema).
5
4
 
@@ -55,7 +54,7 @@ export interface StandardTool<Input, Output, FormattedOutput = Output | { error:
55
54
  description: string;
56
55
  inputSchema?: CombinedSchema<Input>;
57
56
  outputSchema?: CombinedSchema<Output>;
58
- execute(input: Input): FormattedOutput | Promise<FormattedOutput>;
57
+ execute(input: Input, meta?: any): FormattedOutput | Promise<FormattedOutput>;
59
58
  }
60
59
 
61
60
  export function standardTool<Input, Output, FormattedOutput = Output | { error: string }>(def: {
@@ -63,7 +62,7 @@ export function standardTool<Input, Output, FormattedOutput = Output | { error:
63
62
  description: string;
64
63
  inputSchema?: CombinedSchema<Input>;
65
64
  outputSchema?: CombinedSchema<Output>;
66
- execute: (input: Input) => Output | Promise<Output>;
65
+ execute: (input: Input, meta: any) => Output | Promise<Output>; // meta: optional per-call runtime context
67
66
  formatOutput?: (result: Output | Error) => FormattedOutput | Promise<FormattedOutput>;
68
67
  }): StandardTool<Input, Output, FormattedOutput> {
69
68
  const check = async <T>(where: 'input' | 'output', s: CombinedSchema<T>, v: unknown): Promise<T> => {
@@ -80,11 +79,11 @@ export function standardTool<Input, Output, FormattedOutput = Output | { error:
80
79
  description: def.description,
81
80
  inputSchema: def.inputSchema,
82
81
  outputSchema: def.outputSchema,
83
- async execute(input) {
82
+ async execute(input, meta) {
84
83
  let result: Output | Error;
85
84
  try {
86
85
  const validInput = def.inputSchema ? await check('input', def.inputSchema, input) : input;
87
- const output = await def.execute(validInput);
86
+ const output = await def.execute(validInput, meta);
88
87
  result = def.outputSchema ? await check('output', def.outputSchema, output) : output;
89
88
  } catch (e) {
90
89
  result = e instanceof Error ? e : new Error(String(e));
@@ -103,7 +102,7 @@ import { standardTool, type StandardTool, type FormatOutputFn } from 'standard-t
103
102
  standardTool(def): StandardTool<Input, Output, FormattedOutput>;
104
103
  ```
105
104
 
106
- `Input`/`Output` are your **data types** (what your `execute` accepts and returns); the optional schemas describe them. `FormattedOutput` is what the tool hands the model after formatting — `Output | { error: string }` by default.
105
+ `Input`/`Output` are your **data types** (what your `execute` accepts and returns); the optional schemas describe them. `FormattedOutput` is what the tool hands the model after formatting — `Output | { error: string }` by default. `execute` also takes an optional second `meta` argument — per-call runtime context forwarded verbatim to your handler, never validated and never in the JSON Schema (see [Per-call runtime context](#per-call-runtime-context-meta)).
107
106
 
108
107
  | field | type | purpose |
109
108
  | --- | --- | --- |
@@ -111,8 +110,8 @@ standardTool(def): StandardTool<Input, Output, FormattedOutput>;
111
110
  | `description` | `string` | what the tool does |
112
111
  | `inputSchema?` | `CombinedSchema<Input>` | optional input schema — validates **and** emits JSON Schema |
113
112
  | `outputSchema?` | `CombinedSchema<Output>` | optional output schema — validates **and** emits JSON Schema |
114
- | `execute` (yours) | `(input: Input) => Output \| Promise<Output>` | your logic — receives validated input, returns the output |
115
- | `execute` (tool) | `(input: Input) => FormattedOutput \| Promise<FormattedOutput>` | validate in → run yours → validate out → format; errors become the output (no throw) **by default** |
113
+ | `execute` (yours) | `(input: Input, meta: any) => Output \| Promise<Output>` | your logic — receives validated input and the optional per-call `meta`, returns the output |
114
+ | `execute` (tool) | `(input: Input, meta?: any) => FormattedOutput \| Promise<FormattedOutput>` | validate in → run yours (forwarding `meta`) → validate out → format; errors become the output (no throw) **by default** |
116
115
  | `formatOutput?` | `(result: Output \| Error) => FormattedOutput` | optional; maps the result — or an `Error` carrying `issues` — to the model output. Default `result instanceof Error ? { error: result.message } : result` |
117
116
 
118
117
  `inputSchema`/`outputSchema` are optional; when present they must implement both Standard Schema and Standard JSON Schema (Zod 4.2+, ArkType 2.1.28+, or Valibot 1.2+ via `@valibot/to-json-schema`) — `Input`/`Output` are inferred from them (or from `execute` when a schema is omitted).
@@ -140,6 +139,45 @@ const out = await getWeather.execute({ city: 'Paris' }); // { tempC: number } |
140
139
  const parameters = getWeather.inputSchema!['~standard'].jsonSchema.input({ target: 'draft-2020-12' });
141
140
  ```
142
141
 
142
+ ## Per-call runtime context (`meta`)
143
+
144
+ Tools often need per-call data that must **not** appear in the model-facing `inputSchema` — an auth token, a resolver, a request-scoped DB handle. `execute` takes an optional **second `meta` argument**, forwarded verbatim to your handler. It's never validated and never part of the JSON Schema, so your tools can stay **static** (defined once at module scope) while you inject context at call time — instead of closing over it in a per-render factory.
145
+
146
+ `meta` is typed `any`; **annotate it on your handler** to type it at the call site:
147
+
148
+ ```ts
149
+ const greet = standardTool({
150
+ name: 'greet',
151
+ description: 'Greet a person with per-call punctuation',
152
+ inputSchema: z.object({ name: z.string() }),
153
+ execute: ({ name }, meta: { punct: string }) => `hi ${name}${meta.punct}`, // annotate meta here
154
+ });
155
+
156
+ await greet.execute({ name: 'Ada' }, { punct: '!' }); // → 'hi Ada!'
157
+ ```
158
+
159
+ Tools that don't need it just call `execute(input)` — `meta` is optional.
160
+
161
+ ## Throwing instead of the `{ error }` envelope
162
+
163
+ By default a validation failure or a thrown error comes back as `{ error: string }`, so a model loop can keep running. When you'd rather have `execute` **throw** — e.g. to let a caller's `try/catch` handle failures — re-throw the `Error` from `formatOutput`:
164
+
165
+ ```ts
166
+ const getWeather = standardTool({
167
+ name: 'get_weather',
168
+ description: 'Current temperature for a city',
169
+ inputSchema: z.object({ city: z.string() }),
170
+ outputSchema: z.object({ tempC: z.number() }),
171
+ execute: async ({ city }) => ({ tempC: 21 }),
172
+ formatOutput: (result) => {
173
+ if (result instanceof Error) throw result; // validation/exec failures now reject
174
+ return result;
175
+ },
176
+ });
177
+
178
+ await getWeather.execute({ city: 'Paris' }); // { tempC: number } — rejects on bad input/output
179
+ ```
180
+
143
181
  ## With the OpenAI API
144
182
 
145
183
  Uses the [Responses API](https://developers.openai.com/api/docs/guides/function-calling). Because every tool is the same neutral shape, you keep them in one array: `.map` it into the request's `tools`, then dispatch each function call back to the matching tool by `name`. Adding a fourth tool is one more array entry — no special-casing, no per-tool wiring. And because `execute` returns `{ error }` instead of throwing **by default**, a malformed tool call comes back to the model to self-correct rather than crashing your loop (a custom `formatOutput` can opt back into throwing).
package/dist/index.d.ts CHANGED
@@ -27,9 +27,10 @@ export interface StandardTool<Input, Output, FormattedOutput = DefaultFormattedO
27
27
  * Validate input (when `inputSchema`) → run your logic → validate output (when `outputSchema`) →
28
28
  * format. **By default it doesn't throw**: a validation failure or a thrown error becomes the
29
29
  * formatted output (`{ error: string }`) — unless your `formatOutput` throws — so a model loop
30
- * keeps running.
30
+ * keeps running. `meta` is optional per-call runtime context (auth tokens, resolvers, request-scoped
31
+ * data) forwarded verbatim to your handler — never validated, never in the JSON Schema.
31
32
  */
32
- execute(input: Input): FormattedOutput | Promise<FormattedOutput>;
33
+ execute(input: Input, meta?: any): FormattedOutput | Promise<FormattedOutput>;
33
34
  }
34
35
  /**
35
36
  * Create a standard tool. `inputSchema`/`outputSchema` are optional; when present they must implement
@@ -47,7 +48,7 @@ export declare function standardTool<Input, Output, FormattedOutput = DefaultFor
47
48
  description: string;
48
49
  inputSchema?: CombinedSchema<Input>;
49
50
  outputSchema?: CombinedSchema<Output>;
50
- execute: (input: Input) => Output | Promise<Output>;
51
+ execute: (input: Input, meta: any) => Output | Promise<Output>;
51
52
  formatOutput?: FormatOutputFn<Output, FormattedOutput>;
52
53
  }): StandardTool<Input, Output, FormattedOutput>;
53
54
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAoB,MAAM,sBAAsB,CAAC;AAE7E,mBAAmB,sBAAsB,CAAC;AAE1C,mHAAmH;AACnH,MAAM,MAAM,sBAAsB,CAAC,MAAM,IAAI,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAExE;;;;GAIG;AACH,MAAM,MAAM,cAAc,CAAC,MAAM,EAAE,eAAe,IAAI,CACpD,MAAM,EAAE,MAAM,GAAG,KAAK,KACnB,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;AAEhD;;;;;GAKG;AACH,MAAM,WAAW,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,eAAe,GAAG,sBAAsB,CAAC,MAAM,CAAC;IAC3F,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,WAAW,CAAC,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;IACpC,kFAAkF;IAClF,YAAY,CAAC,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACtC;;;;;OAKG;IACH,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;CACnE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,eAAe,GAAG,sBAAsB,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE;IACjG,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;IACpC,YAAY,CAAC,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACtC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACpD,YAAY,CAAC,EAAE,cAAc,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;CACxD,GAAG,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,eAAe,CAAC,CAqB/C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAoB,MAAM,sBAAsB,CAAC;AAE7E,mBAAmB,sBAAsB,CAAC;AAE1C,mHAAmH;AACnH,MAAM,MAAM,sBAAsB,CAAC,MAAM,IAAI,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAExE;;;;GAIG;AACH,MAAM,MAAM,cAAc,CAAC,MAAM,EAAE,eAAe,IAAI,CACpD,MAAM,EAAE,MAAM,GAAG,KAAK,KACnB,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;AAEhD;;;;;GAKG;AACH,MAAM,WAAW,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,eAAe,GAAG,sBAAsB,CAAC,MAAM,CAAC;IAC3F,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,WAAW,CAAC,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;IACpC,kFAAkF;IAClF,YAAY,CAAC,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACtC;;;;;;OAMG;IAEH,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;CAC/E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,eAAe,GAAG,sBAAsB,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE;IACjG,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;IACpC,YAAY,CAAC,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IAEtC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/D,YAAY,CAAC,EAAE,cAAc,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;CACxD,GAAG,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,eAAe,CAAC,CAqB/C"}
package/dist/index.js CHANGED
@@ -17,11 +17,11 @@ export function standardTool(def) {
17
17
  description: def.description,
18
18
  inputSchema: def.inputSchema,
19
19
  outputSchema: def.outputSchema,
20
- async execute(input) {
20
+ async execute(input, meta) {
21
21
  let result;
22
22
  try {
23
23
  const validInput = def.inputSchema ? await validate('input', def.inputSchema, input) : input;
24
- const output = await def.execute(validInput);
24
+ const output = await def.execute(validInput, meta);
25
25
  result = def.outputSchema ? await validate('output', def.outputSchema, output) : output;
26
26
  }
27
27
  catch (error) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAsCA;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAAkE,GAO7F;IACC,MAAM,YAAY,GAChB,GAAG,CAAC,YAAY;QAChB,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,MAAM,CAA+B,CAAC,CAAC;IAC7G,OAAO;QACL,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,YAAY,EAAE,GAAG,CAAC,YAAY;QAC9B,KAAK,CAAC,OAAO,CAAC,KAAK;YACjB,IAAI,MAAsB,CAAC;YAC3B,IAAI,CAAC;gBACH,MAAM,UAAU,GAAG,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;gBAC7F,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;gBAC7C,MAAM,GAAG,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;YAC1F,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YACrE,CAAC;YACD,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC;QAC9B,CAAC;KACF,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,QAAQ,CACrB,MAA0B,EAC1B,MAAS,EACT,KAAc;IAEd,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,4BAA4B;IACtF,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,sFAAsF;QACtF,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,GAAG,MAAM,uBAAuB,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE;YAC/G,MAAM,EAAE,MAAM,CAAC,MAAM;SACtB,CAAC,CAAC;IACL,CAAC;IACD,OAAO,MAAM,CAAC,KAAK,CAAC;AACtB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAwCA;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAAkE,GAQ7F;IACC,MAAM,YAAY,GAChB,GAAG,CAAC,YAAY;QAChB,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,MAAM,CAA+B,CAAC,CAAC;IAC7G,OAAO;QACL,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,YAAY,EAAE,GAAG,CAAC,YAAY;QAC9B,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI;YACvB,IAAI,MAAsB,CAAC;YAC3B,IAAI,CAAC;gBACH,MAAM,UAAU,GAAG,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;gBAC7F,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;gBACnD,MAAM,GAAG,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;YAC1F,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YACrE,CAAC;YACD,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC;QAC9B,CAAC;KACF,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,QAAQ,CACrB,MAA0B,EAC1B,MAAS,EACT,KAAc;IAEd,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,4BAA4B;IACtF,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,sFAAsF;QACtF,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,GAAG,MAAM,uBAAuB,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE;YAC/G,MAAM,EAAE,MAAM,CAAC,MAAM;SACtB,CAAC,CAAC;IACL,CAAC;IACD,OAAO,MAAM,CAAC,KAAK,CAAC;AACtB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "standard-tool",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Standard DRY LLM Tool Interface — name/description/execute with input & output validated via Standard Schema and emitted as JSON Schema via Standard JSON Schema. Zero dependencies.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -51,6 +51,7 @@
51
51
  "devDependencies": {
52
52
  "@biomejs/biome": "^2.4.16",
53
53
  "@types/node": "^25.9.1",
54
- "typescript": "^6.0.2"
54
+ "typescript": "^6.0.2",
55
+ "zod": "^4.4.3"
55
56
  }
56
57
  }