@code-first-agents/tool 0.1.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 +21 -0
- package/README.md +76 -0
- package/dist/args.d.ts +55 -0
- package/dist/args.d.ts.map +1 -0
- package/dist/envelopes.d.ts +141 -0
- package/dist/envelopes.d.ts.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +316 -0
- package/dist/introspection.d.ts +51 -0
- package/dist/introspection.d.ts.map +1 -0
- package/dist/json-schema.d.ts +38 -0
- package/dist/json-schema.d.ts.map +1 -0
- package/dist/output-helpers.d.ts +77 -0
- package/dist/output-helpers.d.ts.map +1 -0
- package/dist/tool-class.d.ts +162 -0
- package/dist/tool-class.d.ts.map +1 -0
- package/dist/types.d.ts +75 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils.d.ts +20 -0
- package/dist/utils.d.ts.map +1 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Juan Gipponi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# @code-first-agents/tool
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
[](https://www.npmjs.com/package/@code-first-agents/tool)
|
|
5
|
+
|
|
6
|
+
Code-first agent tool definitions with Zod schemas.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
bun add @code-first-agents/tool
|
|
12
|
+
# or
|
|
13
|
+
npm install @code-first-agents/tool
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { Tool, l1Output } from "@code-first-agents/tool";
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
|
|
22
|
+
const greet = new Tool({
|
|
23
|
+
name: "greet",
|
|
24
|
+
description: "Say hello",
|
|
25
|
+
input: z.object({ name: z.string() }),
|
|
26
|
+
handler: async ({ input }) => l1Output("pass", `Hello, ${input.name}!`),
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Development
|
|
31
|
+
|
|
32
|
+
**Prerequisites:** [Bun](https://bun.sh) >= 1.0
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
git clone https://github.com/beogip/code-first-agents-tool.git
|
|
36
|
+
cd code-first-agents-tool
|
|
37
|
+
bun install
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
| Command | Description |
|
|
41
|
+
| ---------------- | ------------------------------- |
|
|
42
|
+
| `bun run dev` | Run with file watcher |
|
|
43
|
+
| `bun run build` | Compile to `dist/` (bun + tsc) |
|
|
44
|
+
| `bun test` | Run tests |
|
|
45
|
+
| `bun run check` | Lint + format (Biome, auto-fix) |
|
|
46
|
+
| `bun run lint` | Lint only |
|
|
47
|
+
| `bun run format` | Format only |
|
|
48
|
+
|
|
49
|
+
## Project Structure
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
src/ # Source code
|
|
53
|
+
tests/ # Test files (*.test.ts)
|
|
54
|
+
dist/ # Build output (git-ignored)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Git Hooks
|
|
58
|
+
|
|
59
|
+
[Lefthook](https://github.com/evilmartians/lefthook) runs automatically after `bun install` (via the `prepare` script):
|
|
60
|
+
|
|
61
|
+
- **pre-commit** — Biome checks and auto-fixes staged files
|
|
62
|
+
- **commit-msg** — Validates [Conventional Commits](https://www.conventionalcommits.org/) format
|
|
63
|
+
|
|
64
|
+
## Releases
|
|
65
|
+
|
|
66
|
+
Releases are automated via [semantic-release](https://semantic-release.gitbook.io/) on every push to `main`:
|
|
67
|
+
|
|
68
|
+
- `feat:` → minor release
|
|
69
|
+
- `fix:` → patch release
|
|
70
|
+
- `feat!:` or `BREAKING CHANGE:` → major release
|
|
71
|
+
|
|
72
|
+
The CI workflow handles changelog generation, npm publishing, GitHub releases, and version bumping automatically.
|
|
73
|
+
|
|
74
|
+
## License
|
|
75
|
+
|
|
76
|
+
[MIT](LICENSE)
|
package/dist/args.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* args.ts — CLI argv parsing and input validation for the Tool base class.
|
|
3
|
+
*
|
|
4
|
+
* Contains only the pre-dispatch concerns: turning `process.argv.slice(2)`
|
|
5
|
+
* into a structured `{subcommand, ParsedArgs}` pair and validating the
|
|
6
|
+
* parsed args against a subcommand's Zod input schema.
|
|
7
|
+
*
|
|
8
|
+
* @module code-first-agents-tool/args
|
|
9
|
+
*/
|
|
10
|
+
import type { z } from "zod";
|
|
11
|
+
import type { ParsedArgs } from "./types.ts";
|
|
12
|
+
/**
|
|
13
|
+
* Subcommand names reserved by the base class (`schema`, `help`).
|
|
14
|
+
* Module-private: not re-exported from `./index.ts`.
|
|
15
|
+
*/
|
|
16
|
+
export declare const RESERVED_SUBCOMMANDS: ReadonlySet<string>;
|
|
17
|
+
/**
|
|
18
|
+
* Parse a raw argv slice into subcommand + typed flags + positional args.
|
|
19
|
+
*
|
|
20
|
+
* Rules:
|
|
21
|
+
* - `argv[0]` is the subcommand. A leading `--` prefix is stripped so that
|
|
22
|
+
* `--help` and `help` both resolve to `"help"`. The bare `--` sentinel
|
|
23
|
+
* yields an empty subcommand.
|
|
24
|
+
* - When `argv[0]` is NOT already a reserved builtin, a `--help` or
|
|
25
|
+
* `--schema` flag in the remaining tokens (up to the `--` sentinel)
|
|
26
|
+
* promotes to the subcommand (global-flag override).
|
|
27
|
+
* - Tokens starting with `--` are flag keys; the next token (if not also a
|
|
28
|
+
* flag) is the value. A bare `--flag` at end-of-argv or followed by
|
|
29
|
+
* another `--flag` resolves to `true`.
|
|
30
|
+
* - Repeated `--flag v1 --flag v2` → last-one-wins (`v2`).
|
|
31
|
+
* - The bare `--` token is the end-of-options sentinel (POSIX convention):
|
|
32
|
+
* all subsequent tokens become positional, even if they start with `--`.
|
|
33
|
+
* - Non-flag tokens become positional args in order.
|
|
34
|
+
*
|
|
35
|
+
* Pure function — no I/O, no side effects.
|
|
36
|
+
*
|
|
37
|
+
* @param argv - Array of CLI tokens, typically `process.argv.slice(2)`.
|
|
38
|
+
* @returns Parsed subcommand + {@link ParsedArgs}.
|
|
39
|
+
*/
|
|
40
|
+
export declare function parseArgs(argv: string[]): {
|
|
41
|
+
subcommand: string;
|
|
42
|
+
parsed: ParsedArgs;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Validate raw {@link ParsedArgs} against a subcommand's input Zod schema.
|
|
46
|
+
* Flags are merged as-is; positional args are attached under a reserved `_`
|
|
47
|
+
* key only when present (so strict schemas without `_` stay happy when no
|
|
48
|
+
* positional args are passed).
|
|
49
|
+
*
|
|
50
|
+
* @param parsed - Raw parsed args from {@link parseArgs}.
|
|
51
|
+
* @param inputSchema - The subcommand's input Zod schema.
|
|
52
|
+
* @returns The Zod `safeParse` result carrying either validated data or a structured error.
|
|
53
|
+
*/
|
|
54
|
+
export declare function validateInput<I extends z.ZodTypeAny>(parsed: ParsedArgs, inputSchema: I): z.ZodSafeParseResult<z.core.output<I>>;
|
|
55
|
+
//# sourceMappingURL=args.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,WAAW,CAAC,MAAM,CAA+B,CAAC;AAkBrF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,UAAU,CAAA;CAAE,CA6CpF;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC,0CAMvF"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* envelopes.ts — Typed error-envelope factories for the Tool base class.
|
|
3
|
+
*
|
|
4
|
+
* The four error codes (`unknown_subcommand`, `input_validation_error`,
|
|
5
|
+
* `schema_violation`, `unexpected_error`) are modeled as a discriminated
|
|
6
|
+
* union so extras (`subcommands`, `input_schema`, `detail`) stay type-safe
|
|
7
|
+
* per-code. Each builder owns the exact message template for its code,
|
|
8
|
+
* which previously lived inline in `Tool#run`.
|
|
9
|
+
*
|
|
10
|
+
* @module code-first-agents-tool/envelopes
|
|
11
|
+
*/
|
|
12
|
+
import type { z } from "zod";
|
|
13
|
+
import { type HelpPayload } from "./introspection.ts";
|
|
14
|
+
import type { SubcommandSpec } from "./types.ts";
|
|
15
|
+
/**
|
|
16
|
+
* Discriminated union of every error envelope the base class can emit.
|
|
17
|
+
* Each variant is assignable to `ToolOutput` from `./utils.ts` via its
|
|
18
|
+
* index signature, so `jsonOutput` consumes them without casts.
|
|
19
|
+
*
|
|
20
|
+
* The final variant (`error: string`) covers handler-emitted business
|
|
21
|
+
* errors thrown via {@link ToolError} — the `error` code is whatever the
|
|
22
|
+
* handler chose.
|
|
23
|
+
*/
|
|
24
|
+
export type ErrorEnvelope = {
|
|
25
|
+
ok: false;
|
|
26
|
+
error: "unknown_subcommand";
|
|
27
|
+
message: string;
|
|
28
|
+
subcommands: HelpPayload;
|
|
29
|
+
} | {
|
|
30
|
+
ok: false;
|
|
31
|
+
error: "input_validation_error";
|
|
32
|
+
message: string;
|
|
33
|
+
detail: string;
|
|
34
|
+
input_schema: unknown;
|
|
35
|
+
} | {
|
|
36
|
+
ok: false;
|
|
37
|
+
error: "schema_violation";
|
|
38
|
+
message: string;
|
|
39
|
+
detail: string;
|
|
40
|
+
} | {
|
|
41
|
+
ok: false;
|
|
42
|
+
error: "non_object_return";
|
|
43
|
+
message: string;
|
|
44
|
+
} | {
|
|
45
|
+
ok: false;
|
|
46
|
+
error: "unexpected_error";
|
|
47
|
+
message: string;
|
|
48
|
+
detail?: string | Record<string, unknown>;
|
|
49
|
+
} | {
|
|
50
|
+
ok: false;
|
|
51
|
+
error: string;
|
|
52
|
+
message: string;
|
|
53
|
+
detail?: string | Record<string, unknown>;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Error class a handler can throw to emit a structured error envelope with
|
|
57
|
+
* a business-specific `error` code (e.g. `"path_not_found"`, `"rate_limited"`).
|
|
58
|
+
* The base class catches instances of this class and produces the envelope
|
|
59
|
+
* via {@link toolErrorEnvelope}; anything else thrown becomes an
|
|
60
|
+
* `unexpected_error` envelope.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* handler: ({ path }) => {
|
|
64
|
+
* if (!existsSync(path)) {
|
|
65
|
+
* throw new ToolError("path_not_found", `Path does not exist: ${path}`);
|
|
66
|
+
* }
|
|
67
|
+
* return { message: "ok", ... };
|
|
68
|
+
* }
|
|
69
|
+
*/
|
|
70
|
+
export declare class ToolError extends Error {
|
|
71
|
+
/** Machine-readable error code emitted in the envelope's `error` field. */
|
|
72
|
+
readonly code: string;
|
|
73
|
+
/** Optional extra context included in the envelope's `detail` field. */
|
|
74
|
+
readonly detail?: string | Record<string, unknown>;
|
|
75
|
+
/**
|
|
76
|
+
* @param code - Machine-readable error code (emitted verbatim). Conventionally snake_case.
|
|
77
|
+
* @param message - Human-readable message.
|
|
78
|
+
* @param detail - Optional extra context (string or structured object).
|
|
79
|
+
*/
|
|
80
|
+
constructor(code: string, message: string, detail?: string | Record<string, unknown>);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Build the `unknown_subcommand` envelope. When `name === ""` (empty argv),
|
|
84
|
+
* the message notes the absence; otherwise it names the unrecognized input.
|
|
85
|
+
* Always includes the full `subcommands` listing so an LLM caller can
|
|
86
|
+
* self-correct on retry.
|
|
87
|
+
*
|
|
88
|
+
* @param name - The unrecognized subcommand (or empty string for empty argv).
|
|
89
|
+
* @param subs - The Tool's registered-subcommands map.
|
|
90
|
+
* @returns A typed `unknown_subcommand` envelope.
|
|
91
|
+
*/
|
|
92
|
+
export declare function unknownSubcommandEnvelope(name: string, subs: ReadonlyMap<string, SubcommandSpec<z.ZodTypeAny, z.ZodTypeAny>>): ErrorEnvelope;
|
|
93
|
+
/**
|
|
94
|
+
* Build the `input_validation_error` envelope. Attaches the input JSON
|
|
95
|
+
* Schema (or `$error` fallback) so the LLM can correct the flags and retry.
|
|
96
|
+
*
|
|
97
|
+
* @param name - The subcommand whose input failed validation.
|
|
98
|
+
* @param zerr - The Zod error produced by `safeParse`.
|
|
99
|
+
* @param inputSchema - The subcommand's declared input Zod schema.
|
|
100
|
+
* @returns A typed `input_validation_error` envelope.
|
|
101
|
+
*/
|
|
102
|
+
export declare function inputValidationErrorEnvelope(name: string, zerr: z.ZodError, inputSchema: z.ZodTypeAny): ErrorEnvelope;
|
|
103
|
+
/**
|
|
104
|
+
* Build the `schema_violation` envelope. Fires when a handler returns a
|
|
105
|
+
* value that fails the subcommand's output Zod schema.
|
|
106
|
+
*
|
|
107
|
+
* @param name - The subcommand whose handler return failed validation.
|
|
108
|
+
* @param zerr - The Zod error produced by output `safeParse`.
|
|
109
|
+
* @returns A typed `schema_violation` envelope.
|
|
110
|
+
*/
|
|
111
|
+
export declare function schemaViolationEnvelope(name: string, zerr: z.ZodError): ErrorEnvelope;
|
|
112
|
+
/**
|
|
113
|
+
* Build the `unexpected_error` envelope. Catches anything the handler (or
|
|
114
|
+
* the base class plumbing itself) throws that is NOT a {@link ToolError}.
|
|
115
|
+
* The error's stack is included in `detail` when available.
|
|
116
|
+
*
|
|
117
|
+
* @param err - The thrown value (or rejected Promise reason).
|
|
118
|
+
* @returns A typed `unexpected_error` envelope.
|
|
119
|
+
*/
|
|
120
|
+
export declare function unexpectedErrorEnvelope(err: unknown): ErrorEnvelope;
|
|
121
|
+
/**
|
|
122
|
+
* Build a handler-emitted error envelope from a {@link ToolError}. Preserves
|
|
123
|
+
* the custom `code`, `message`, and optional `detail` the handler declared,
|
|
124
|
+
* so consumers see a business-specific error envelope instead of a generic
|
|
125
|
+
* `unexpected_error`.
|
|
126
|
+
*
|
|
127
|
+
* @param err - The {@link ToolError} the handler threw.
|
|
128
|
+
* @returns An envelope carrying the handler's custom `error` code.
|
|
129
|
+
*/
|
|
130
|
+
export declare function toolErrorEnvelope(err: ToolError): ErrorEnvelope;
|
|
131
|
+
/**
|
|
132
|
+
* Build the `non_object_return` envelope. Fires when a handler returns a
|
|
133
|
+
* non-plain-object value (null, string, array, etc.) that cannot be spread
|
|
134
|
+
* into the output envelope.
|
|
135
|
+
*
|
|
136
|
+
* @param name - The subcommand whose handler returned a non-object.
|
|
137
|
+
* @param value - The actual value the handler returned (used to describe the type).
|
|
138
|
+
* @returns A typed `non_object_return` envelope.
|
|
139
|
+
*/
|
|
140
|
+
export declare function nonObjectReturnEnvelope(name: string, value: unknown): ErrorEnvelope;
|
|
141
|
+
//# sourceMappingURL=envelopes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"envelopes.d.ts","sourceRoot":"","sources":["../src/envelopes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,EAAoB,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAExE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGjD;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,GACrB;IACE,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,oBAAoB,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,WAAW,CAAC;CAC1B,GACD;IACE,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,wBAAwB,CAAC;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,OAAO,CAAC;CACvB,GACD;IACE,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,kBAAkB,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,GACD;IACE,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,mBAAmB,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB,GACD;IACE,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,kBAAkB,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC3C,GACD;IACE,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC3C,CAAC;AAEN;;;;;;;;;;;;;;GAcG;AACH,qBAAa,SAAU,SAAQ,KAAK;IAClC,2EAA2E;IAC3E,SAAgB,IAAI,EAAE,MAAM,CAAC;IAC7B,wEAAwE;IACxE,SAAgB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAE1D;;;;OAIG;gBACS,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAQrF;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,WAAW,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,GACpE,aAAa,CAWf;AAED;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,CAAC,CAAC,QAAQ,EAChB,WAAW,EAAE,CAAC,CAAC,UAAU,GACxB,aAAa,CAUf;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,QAAQ,GAAG,aAAa,CAOrF;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,aAAa,CAMnE;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,SAAS,GAAG,aAAa,CAI/D;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,aAAa,CAOnF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* index.ts — Barrel for the code-first-agents Tool base class.
|
|
3
|
+
*
|
|
4
|
+
* Implementation lives in `src/*.ts`. This file re-exports the public API
|
|
5
|
+
* so consumers import from the package root:
|
|
6
|
+
*
|
|
7
|
+
* import { Tool, l1Output, parseArgs, validateInput } from "@code-first-agents/tool";
|
|
8
|
+
*
|
|
9
|
+
* @module @code-first-agents/tool
|
|
10
|
+
*/
|
|
11
|
+
export { parseArgs, validateInput } from "./args.ts";
|
|
12
|
+
export type { ErrorEnvelope } from "./envelopes.ts";
|
|
13
|
+
export { ToolError } from "./envelopes.ts";
|
|
14
|
+
export type { HelpPayload, HelpPayloadEntry, SchemaOutputEntry, } from "./introspection.ts";
|
|
15
|
+
export { buildHelpPayload, buildSchemaOutput } from "./introspection.ts";
|
|
16
|
+
export type { JSONSchemaResult } from "./json-schema.ts";
|
|
17
|
+
export { l1Output, l2Output, l3Output } from "./output-helpers.ts";
|
|
18
|
+
export { Tool } from "./tool-class.ts";
|
|
19
|
+
export type { HandlerReturn, ParsedArgs, SubcommandSpec, ToolMeta, } from "./types.ts";
|
|
20
|
+
export type { ToolOutput } from "./utils.ts";
|
|
21
|
+
export { jsonOutput, stringifyError } from "./utils.ts";
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AACrD,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,YAAY,EACV,WAAW,EACX,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACzE,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACnE,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,YAAY,EACV,aAAa,EACb,UAAU,EACV,cAAc,EACd,QAAQ,GACT,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
// src/args.ts
|
|
2
|
+
var RESERVED_SUBCOMMANDS = new Set(["schema", "help"]);
|
|
3
|
+
function normalizeSubcommand(raw) {
|
|
4
|
+
if (raw === undefined || raw === "--")
|
|
5
|
+
return "";
|
|
6
|
+
return raw.startsWith("--") ? raw.slice(2) : raw;
|
|
7
|
+
}
|
|
8
|
+
function parseArgs(argv) {
|
|
9
|
+
let subcommand = normalizeSubcommand(argv[0]);
|
|
10
|
+
const flags = {};
|
|
11
|
+
const positional = [];
|
|
12
|
+
let i = 1;
|
|
13
|
+
while (i < argv.length) {
|
|
14
|
+
const tok = argv[i];
|
|
15
|
+
if (tok === "--") {
|
|
16
|
+
positional.push(...argv.slice(i + 1));
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
if (!tok.startsWith("--")) {
|
|
20
|
+
positional.push(tok);
|
|
21
|
+
i += 1;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const key = tok.slice(2);
|
|
25
|
+
if (RESERVED_SUBCOMMANDS.has(key) && !RESERVED_SUBCOMMANDS.has(subcommand)) {
|
|
26
|
+
subcommand = key;
|
|
27
|
+
i += 1;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const next = argv[i + 1];
|
|
31
|
+
if (next === undefined || next.startsWith("--")) {
|
|
32
|
+
flags[key] = true;
|
|
33
|
+
i += 1;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
flags[key] = next;
|
|
37
|
+
i += 2;
|
|
38
|
+
}
|
|
39
|
+
return { subcommand, parsed: { flags, positional } };
|
|
40
|
+
}
|
|
41
|
+
function validateInput(parsed, inputSchema) {
|
|
42
|
+
const toValidate = { ...parsed.flags };
|
|
43
|
+
if (parsed.positional.length > 0) {
|
|
44
|
+
toValidate._ = parsed.positional;
|
|
45
|
+
}
|
|
46
|
+
return inputSchema.safeParse(toValidate);
|
|
47
|
+
}
|
|
48
|
+
// src/json-schema.ts
|
|
49
|
+
import { z } from "zod";
|
|
50
|
+
function safeToJSONSchema(schema) {
|
|
51
|
+
try {
|
|
52
|
+
return { ok: true, schema: z.toJSONSchema(schema, { target: "draft-2020-12" }) };
|
|
53
|
+
} catch (err) {
|
|
54
|
+
return { ok: false, $error: err instanceof Error ? err.message : String(err) };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/introspection.ts
|
|
59
|
+
function buildSchemaOutput(subs) {
|
|
60
|
+
const result = {};
|
|
61
|
+
for (const [name, spec] of subs) {
|
|
62
|
+
const inputResult = safeToJSONSchema(spec.input);
|
|
63
|
+
const outputResult = safeToJSONSchema(spec.output);
|
|
64
|
+
if (!inputResult.ok) {
|
|
65
|
+
result[name] = { $error: inputResult.$error };
|
|
66
|
+
} else if (!outputResult.ok) {
|
|
67
|
+
result[name] = { $error: outputResult.$error };
|
|
68
|
+
} else {
|
|
69
|
+
result[name] = { input: inputResult.schema, output: outputResult.schema };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
function buildHelpPayload(subs) {
|
|
75
|
+
const result = [];
|
|
76
|
+
for (const [name, spec] of subs) {
|
|
77
|
+
const conv = safeToJSONSchema(spec.input);
|
|
78
|
+
const input_schema = conv.ok ? conv.schema : { $error: conv.$error };
|
|
79
|
+
result.push({ name, description: spec.description, input_schema });
|
|
80
|
+
}
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/utils.ts
|
|
85
|
+
function jsonOutput(data) {
|
|
86
|
+
console.log(JSON.stringify(data));
|
|
87
|
+
process.exit(0);
|
|
88
|
+
}
|
|
89
|
+
function stringifyError(err) {
|
|
90
|
+
return err instanceof Error ? err.message : String(err);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/envelopes.ts
|
|
94
|
+
class ToolError extends Error {
|
|
95
|
+
code;
|
|
96
|
+
detail;
|
|
97
|
+
constructor(code, message, detail) {
|
|
98
|
+
super(message);
|
|
99
|
+
this.name = "ToolError";
|
|
100
|
+
this.code = code;
|
|
101
|
+
if (detail !== undefined) {
|
|
102
|
+
this.detail = detail;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function unknownSubcommandEnvelope(name, subs) {
|
|
107
|
+
const message = name === "" ? "No subcommand provided — see 'subcommands' for available options" : `Unknown subcommand '${name}' — see 'subcommands' for available options`;
|
|
108
|
+
return {
|
|
109
|
+
ok: false,
|
|
110
|
+
error: "unknown_subcommand",
|
|
111
|
+
message,
|
|
112
|
+
subcommands: buildHelpPayload(subs)
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function inputValidationErrorEnvelope(name, zerr, inputSchema) {
|
|
116
|
+
const conv = safeToJSONSchema(inputSchema);
|
|
117
|
+
const input_schema = conv.ok ? conv.schema : { $error: conv.$error };
|
|
118
|
+
return {
|
|
119
|
+
ok: false,
|
|
120
|
+
error: "input_validation_error",
|
|
121
|
+
message: `Input validation failed for subcommand '${name}'`,
|
|
122
|
+
detail: zerr.message,
|
|
123
|
+
input_schema
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function schemaViolationEnvelope(name, zerr) {
|
|
127
|
+
return {
|
|
128
|
+
ok: false,
|
|
129
|
+
error: "schema_violation",
|
|
130
|
+
message: `Handler output failed schema validation for subcommand '${name}'`,
|
|
131
|
+
detail: zerr.message
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function unexpectedErrorEnvelope(err) {
|
|
135
|
+
const message = stringifyError(err);
|
|
136
|
+
const stack = err instanceof Error ? err.stack : undefined;
|
|
137
|
+
return stack !== undefined ? { ok: false, error: "unexpected_error", message, detail: stack } : { ok: false, error: "unexpected_error", message };
|
|
138
|
+
}
|
|
139
|
+
function toolErrorEnvelope(err) {
|
|
140
|
+
return err.detail !== undefined ? { ok: false, error: err.code, message: err.message, detail: err.detail } : { ok: false, error: err.code, message: err.message };
|
|
141
|
+
}
|
|
142
|
+
function nonObjectReturnEnvelope(name, value) {
|
|
143
|
+
const type = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
|
|
144
|
+
return {
|
|
145
|
+
ok: false,
|
|
146
|
+
error: "non_object_return",
|
|
147
|
+
message: `Handler for '${name}' must return a plain object, got ${type}`
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
// src/output-helpers.ts
|
|
151
|
+
import { z as z2 } from "zod";
|
|
152
|
+
function l1Output(fields) {
|
|
153
|
+
return z2.object({
|
|
154
|
+
ok: z2.literal(true),
|
|
155
|
+
message: z2.string(),
|
|
156
|
+
...fields
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
function l2Output(classification, fields) {
|
|
160
|
+
return z2.object({
|
|
161
|
+
ok: z2.literal(true),
|
|
162
|
+
message: z2.string(),
|
|
163
|
+
classification,
|
|
164
|
+
...fields ?? {}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
function l3Output(fields) {
|
|
168
|
+
return z2.object({
|
|
169
|
+
ok: z2.literal(true),
|
|
170
|
+
message: z2.string(),
|
|
171
|
+
instructions: z2.string(),
|
|
172
|
+
...fields ?? {}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
// src/tool-class.ts
|
|
176
|
+
import { z as z3 } from "zod";
|
|
177
|
+
function isPlainObject(value) {
|
|
178
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
class Tool {
|
|
182
|
+
meta;
|
|
183
|
+
subs = new Map;
|
|
184
|
+
constructor(meta) {
|
|
185
|
+
this.meta = meta;
|
|
186
|
+
}
|
|
187
|
+
subcommand(spec) {
|
|
188
|
+
this.validateRegistration(spec);
|
|
189
|
+
this.subs.set(spec.name, spec);
|
|
190
|
+
return this;
|
|
191
|
+
}
|
|
192
|
+
async run(argv) {
|
|
193
|
+
try {
|
|
194
|
+
const { subcommand, parsed } = parseArgs(argv);
|
|
195
|
+
const builtin = this.dispatchBuiltin(subcommand);
|
|
196
|
+
if (builtin)
|
|
197
|
+
return jsonOutput(builtin);
|
|
198
|
+
const miss = this.rejectUnknown(subcommand);
|
|
199
|
+
if (miss)
|
|
200
|
+
return jsonOutput(miss);
|
|
201
|
+
return jsonOutput(await this.runSubcommand(subcommand, parsed));
|
|
202
|
+
} catch (err) {
|
|
203
|
+
if (err instanceof ToolError)
|
|
204
|
+
return jsonOutput(toolErrorEnvelope(err));
|
|
205
|
+
return jsonOutput(unexpectedErrorEnvelope(err));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
validateRegistration(spec) {
|
|
209
|
+
if (RESERVED_SUBCOMMANDS.has(spec.name)) {
|
|
210
|
+
throw new RangeError(`Subcommand name "${spec.name}" is reserved — the base class auto-registers 'schema' and 'help'`);
|
|
211
|
+
}
|
|
212
|
+
if (this.subs.has(spec.name)) {
|
|
213
|
+
throw new RangeError(`Subcommand "${spec.name}" is already registered`);
|
|
214
|
+
}
|
|
215
|
+
if (spec.input === undefined || spec.input === null) {
|
|
216
|
+
throw new TypeError(`Subcommand "${spec.name}" is missing required 'input' Zod schema (use z.object({}).strict() for argless subs)`);
|
|
217
|
+
}
|
|
218
|
+
if (spec.output === undefined || spec.output === null) {
|
|
219
|
+
throw new TypeError(`Subcommand "${spec.name}" is missing required 'output' Zod schema (use l1Output({}) for minimal output)`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
dispatchBuiltin(name) {
|
|
223
|
+
if (name === "schema") {
|
|
224
|
+
return {
|
|
225
|
+
ok: true,
|
|
226
|
+
message: this.subs.size === 0 ? `Tool "${this.meta.name}" has no subcommands registered` : `JSON Schemas for ${this.subs.size} subcommand(s)`,
|
|
227
|
+
schemas: buildSchemaOutput(this.subs)
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
if (name === "help") {
|
|
231
|
+
return {
|
|
232
|
+
ok: true,
|
|
233
|
+
message: this.meta.description || `Help for ${this.meta.name}`,
|
|
234
|
+
tool: { name: this.meta.name },
|
|
235
|
+
subcommands: buildHelpPayload(this.subs)
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
rejectUnknown(name) {
|
|
241
|
+
if (name === "" || !this.subs.has(name)) {
|
|
242
|
+
return unknownSubcommandEnvelope(name, this.subs);
|
|
243
|
+
}
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
async runSubcommand(name, parsed) {
|
|
247
|
+
const spec = this.subs.get(name);
|
|
248
|
+
if (!spec) {
|
|
249
|
+
return unexpectedErrorEnvelope(new Error(`Subcommand '${name}' vanished between rejectUnknown and runSubcommand`));
|
|
250
|
+
}
|
|
251
|
+
const inputResult = validateInput(parsed, spec.input);
|
|
252
|
+
if (!inputResult.success) {
|
|
253
|
+
return inputValidationErrorEnvelope(name, inputResult.error, spec.input);
|
|
254
|
+
}
|
|
255
|
+
const handlerResult = await spec.handler(inputResult.data);
|
|
256
|
+
return this.validateOutput(name, handlerResult, spec);
|
|
257
|
+
}
|
|
258
|
+
async invoke(subcommand, args = {}) {
|
|
259
|
+
try {
|
|
260
|
+
const builtin = this.dispatchBuiltin(subcommand);
|
|
261
|
+
if (builtin)
|
|
262
|
+
return builtin;
|
|
263
|
+
const miss = this.rejectUnknown(subcommand);
|
|
264
|
+
if (miss)
|
|
265
|
+
return miss;
|
|
266
|
+
const spec = this.subs.get(subcommand);
|
|
267
|
+
if (!spec) {
|
|
268
|
+
return unexpectedErrorEnvelope(new Error(`Subcommand '${subcommand}' vanished between rejectUnknown and invoke`));
|
|
269
|
+
}
|
|
270
|
+
const inputResult = spec.input.safeParse(args);
|
|
271
|
+
if (!inputResult.success) {
|
|
272
|
+
return inputValidationErrorEnvelope(subcommand, inputResult.error, spec.input);
|
|
273
|
+
}
|
|
274
|
+
const handlerResult = await spec.handler(inputResult.data);
|
|
275
|
+
return this.validateOutput(subcommand, handlerResult, spec);
|
|
276
|
+
} catch (err) {
|
|
277
|
+
if (err instanceof ToolError)
|
|
278
|
+
return toolErrorEnvelope(err);
|
|
279
|
+
return unexpectedErrorEnvelope(err);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
validateOutput(name, handlerResult, spec) {
|
|
283
|
+
if (!isPlainObject(handlerResult)) {
|
|
284
|
+
return nonObjectReturnEnvelope(name, handlerResult);
|
|
285
|
+
}
|
|
286
|
+
const fullResult = { ...handlerResult, ok: true };
|
|
287
|
+
const outputResult = spec.output.safeParse(fullResult);
|
|
288
|
+
if (!outputResult.success) {
|
|
289
|
+
return schemaViolationEnvelope(name, outputResult.error);
|
|
290
|
+
}
|
|
291
|
+
const data = outputResult.data;
|
|
292
|
+
if (!("ok" in data)) {
|
|
293
|
+
return schemaViolationEnvelope(name, new z3.ZodError([
|
|
294
|
+
{
|
|
295
|
+
code: "custom",
|
|
296
|
+
path: ["ok"],
|
|
297
|
+
message: "Output schema is missing 'ok' field — add ok: z.literal(true) or use l1Output/l2Output/l3Output helpers"
|
|
298
|
+
}
|
|
299
|
+
]));
|
|
300
|
+
}
|
|
301
|
+
return data;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
export {
|
|
305
|
+
validateInput,
|
|
306
|
+
stringifyError,
|
|
307
|
+
parseArgs,
|
|
308
|
+
l3Output,
|
|
309
|
+
l2Output,
|
|
310
|
+
l1Output,
|
|
311
|
+
jsonOutput,
|
|
312
|
+
buildSchemaOutput,
|
|
313
|
+
buildHelpPayload,
|
|
314
|
+
ToolError,
|
|
315
|
+
Tool
|
|
316
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* introspection.ts — Builders for the auto-registered `schema` and `help`
|
|
3
|
+
* subcommand payloads, plus the help listing carried inside the
|
|
4
|
+
* `unknown_subcommand` error envelope.
|
|
5
|
+
*
|
|
6
|
+
* Both builders delegate to `safeToJSONSchema` so exotic Zod constructs
|
|
7
|
+
* fail soft (one subcommand emits `{$error}` instead of crashing the whole
|
|
8
|
+
* response).
|
|
9
|
+
*
|
|
10
|
+
* @module code-first-agents-tool/introspection
|
|
11
|
+
*/
|
|
12
|
+
import type { z } from "zod";
|
|
13
|
+
import type { SubcommandSpec } from "./types.ts";
|
|
14
|
+
/** One entry in the `subcommands` help listing. */
|
|
15
|
+
export interface HelpPayloadEntry {
|
|
16
|
+
/** Subcommand name as registered. */
|
|
17
|
+
name: string;
|
|
18
|
+
/** One-line description from the spec. */
|
|
19
|
+
description: string;
|
|
20
|
+
/** Input JSON Schema (draft-2020-12) or `{$error}` fallback. */
|
|
21
|
+
input_schema: unknown;
|
|
22
|
+
}
|
|
23
|
+
/** The shape returned by {@link buildHelpPayload} — an array of entries. */
|
|
24
|
+
export type HelpPayload = HelpPayloadEntry[];
|
|
25
|
+
/** One entry in the `schemas` map emitted by the `schema` subcommand. */
|
|
26
|
+
export type SchemaOutputEntry = {
|
|
27
|
+
input: unknown;
|
|
28
|
+
output: unknown;
|
|
29
|
+
} | {
|
|
30
|
+
$error: string;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Convert a registered subcommand map into a `{name: {input, output}}`
|
|
34
|
+
* record of JSON Schemas (draft-2020-12). Per-subcommand conversion is
|
|
35
|
+
* fail-soft via {@link safeToJSONSchema}: a failing sub gets a `$error`
|
|
36
|
+
* entry instead of crashing the whole `schema` response.
|
|
37
|
+
*
|
|
38
|
+
* @param subs - Map from subcommand name to its spec.
|
|
39
|
+
* @returns Record keyed by subcommand name, each value either `{input, output}` or `{$error}`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function buildSchemaOutput(subs: ReadonlyMap<string, SubcommandSpec<z.ZodTypeAny, z.ZodTypeAny>>): Record<string, SchemaOutputEntry>;
|
|
42
|
+
/**
|
|
43
|
+
* Build the `subcommands` array used by `help` output and the
|
|
44
|
+
* `unknown_subcommand` error envelope. Each entry carries the input JSON
|
|
45
|
+
* Schema so an LLM caller can discover which flags a subcommand expects.
|
|
46
|
+
*
|
|
47
|
+
* @param subs - Map from subcommand name to its spec.
|
|
48
|
+
* @returns Array of `{name, description, input_schema}` entries.
|
|
49
|
+
*/
|
|
50
|
+
export declare function buildHelpPayload(subs: ReadonlyMap<string, SubcommandSpec<z.ZodTypeAny, z.ZodTypeAny>>): HelpPayload;
|
|
51
|
+
//# sourceMappingURL=introspection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"introspection.d.ts","sourceRoot":"","sources":["../src/introspection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,mDAAmD;AACnD,MAAM,WAAW,gBAAgB;IAC/B,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,0CAA0C;IAC1C,WAAW,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,4EAA4E;AAC5E,MAAM,MAAM,WAAW,GAAG,gBAAgB,EAAE,CAAC;AAE7C,yEAAyE;AACzE,MAAM,MAAM,iBAAiB,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzF;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,WAAW,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,GACpE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAcnC;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,WAAW,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,GACpE,WAAW,CAQb"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* json-schema.ts — Safe wrapper around `z.toJSONSchema`.
|
|
3
|
+
*
|
|
4
|
+
* `z.toJSONSchema` throws on exotic Zod constructs (e.g. `.transform()`).
|
|
5
|
+
* This helper centralizes the try/catch and the target version
|
|
6
|
+
* (`draft-2020-12`) so the three call sites — `buildSchemaOutput`,
|
|
7
|
+
* `buildHelpPayload`, and the `input_validation_error` envelope builder —
|
|
8
|
+
* share a single fail-soft path.
|
|
9
|
+
*
|
|
10
|
+
* **Known limitation:** Zod v4's `toJSONSchema` emits `required` for fields
|
|
11
|
+
* that have `.default(...)`. The emitted JSON Schema says the field is
|
|
12
|
+
* mandatory even though Zod's runtime validation accepts its absence and
|
|
13
|
+
* fills the default. This can mislead LLM callers reading `input_schema`
|
|
14
|
+
* for self-correction (they may conclude a defaulted flag is required).
|
|
15
|
+
* No workaround applied — consumers should test with the actual tool, not
|
|
16
|
+
* rely on the schema's `required` array for defaulted fields.
|
|
17
|
+
*
|
|
18
|
+
* @module code-first-agents-tool/json-schema
|
|
19
|
+
*/
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
/** Result of converting a Zod schema to JSON Schema — success carries the schema; failure carries a message. */
|
|
22
|
+
export type JSONSchemaResult = {
|
|
23
|
+
ok: true;
|
|
24
|
+
schema: unknown;
|
|
25
|
+
} | {
|
|
26
|
+
ok: false;
|
|
27
|
+
$error: string;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Convert a Zod schema to JSON Schema (draft-2020-12) without throwing.
|
|
31
|
+
* On success returns `{ ok: true, schema }`; on failure returns
|
|
32
|
+
* `{ ok: false, $error }` carrying the thrown message.
|
|
33
|
+
*
|
|
34
|
+
* @param schema - Any Zod schema.
|
|
35
|
+
* @returns A discriminated result — never throws.
|
|
36
|
+
*/
|
|
37
|
+
export declare function safeToJSONSchema(schema: z.ZodTypeAny): JSONSchemaResult;
|
|
38
|
+
//# sourceMappingURL=json-schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-schema.d.ts","sourceRoot":"","sources":["../src/json-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,gHAAgH;AAChH,MAAM,MAAM,gBAAgB,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7F;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,GAAG,gBAAgB,CAMvE"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* output-helpers.ts — Level-specific output-schema composers for the
|
|
3
|
+
* code-first-agents Tool contract.
|
|
4
|
+
*
|
|
5
|
+
* Every tool output includes the `ok: z.literal(true)` + `message: z.string()`
|
|
6
|
+
* envelope. These helpers bake that in and add the fields the spec requires
|
|
7
|
+
* for each level (none for L1; required `classification` enum for L2;
|
|
8
|
+
* required `instructions` string for L3), so the caller can't forget them
|
|
9
|
+
* and VS Code shows a fully-resolved handler return type.
|
|
10
|
+
*
|
|
11
|
+
* Opt-in: tools with shapes that don't fit a level may pass a raw
|
|
12
|
+
* `z.object({...})` to `output`. The base class doesn't require a helper.
|
|
13
|
+
*
|
|
14
|
+
* @module code-first-agents-tool/output-helpers
|
|
15
|
+
*/
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
/**
|
|
18
|
+
* Compose an **L1 (data)** output schema: envelope + arbitrary raw fields.
|
|
19
|
+
* Use when the tool returns facts for the LLM to interpret.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* const schema = l1Output({ checkboxes: z.number(), file_paths: z.number() });
|
|
23
|
+
*
|
|
24
|
+
* @param fields - Raw data fields to include alongside the envelope.
|
|
25
|
+
* @returns A `z.object` carrying `ok`, `message`, and the caller's fields.
|
|
26
|
+
*/
|
|
27
|
+
export declare function l1Output<T extends z.ZodRawShape>(fields: T): z.ZodObject<{
|
|
28
|
+
ok: z.ZodLiteral<true>;
|
|
29
|
+
message: z.ZodString;
|
|
30
|
+
} & T extends infer T_1 ? { -readonly [P in keyof T_1]: T_1[P]; } : never, z.core.$strip>;
|
|
31
|
+
/**
|
|
32
|
+
* Compose an **L2 (classification)** output schema: envelope + a required
|
|
33
|
+
* `classification` enum + optional extras. Use when the tool returns a
|
|
34
|
+
* discrete category the skill can branch on.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* const schema = l2Output(
|
|
38
|
+
* z.enum(["lean", "standard", "full"]),
|
|
39
|
+
* { score: z.number(), signals: z.object({ checkboxes: z.number() }) },
|
|
40
|
+
* );
|
|
41
|
+
*
|
|
42
|
+
* @param classification - Zod enum describing the discrete classification result.
|
|
43
|
+
* @param fields - Optional extras (e.g. score, raw signals) merged into the output.
|
|
44
|
+
* @returns A `z.object` carrying `ok`, `message`, `classification`, and extras.
|
|
45
|
+
*/
|
|
46
|
+
export declare function l2Output<C extends z.ZodTypeAny, T extends z.ZodRawShape>(classification: C, fields: T): z.ZodObject<{
|
|
47
|
+
ok: z.ZodLiteral<true>;
|
|
48
|
+
message: z.ZodString;
|
|
49
|
+
classification: C;
|
|
50
|
+
} & T>;
|
|
51
|
+
export declare function l2Output<C extends z.ZodTypeAny>(classification: C): z.ZodObject<{
|
|
52
|
+
ok: z.ZodLiteral<true>;
|
|
53
|
+
message: z.ZodString;
|
|
54
|
+
classification: C;
|
|
55
|
+
}>;
|
|
56
|
+
/**
|
|
57
|
+
* Compose an **L3 (instructions)** output schema: envelope + a required
|
|
58
|
+
* `instructions` string + optional extras. Use when the tool builds a
|
|
59
|
+
* verbatim procedure for the LLM to execute.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* const schema = l3Output({ plan_level: z.enum(["lean", "standard", "full"]) });
|
|
63
|
+
*
|
|
64
|
+
* @param fields - Optional extras (e.g. classification alongside instructions) merged into the output.
|
|
65
|
+
* @returns A `z.object` carrying `ok`, `message`, `instructions`, and extras.
|
|
66
|
+
*/
|
|
67
|
+
export declare function l3Output<T extends z.ZodRawShape>(fields: T): z.ZodObject<{
|
|
68
|
+
ok: z.ZodLiteral<true>;
|
|
69
|
+
message: z.ZodString;
|
|
70
|
+
instructions: z.ZodString;
|
|
71
|
+
} & T>;
|
|
72
|
+
export declare function l3Output(): z.ZodObject<{
|
|
73
|
+
ok: z.ZodLiteral<true>;
|
|
74
|
+
message: z.ZodString;
|
|
75
|
+
instructions: z.ZodString;
|
|
76
|
+
}>;
|
|
77
|
+
//# sourceMappingURL=output-helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output-helpers.d.ts","sourceRoot":"","sources":["../src/output-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,MAAM,EAAE,CAAC;;;0FAM1D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,EAAE,CAAC,SAAS,CAAC,CAAC,WAAW,EACtE,cAAc,EAAE,CAAC,EACjB,MAAM,EAAE,CAAC,GACR,CAAC,CAAC,SAAS,CAAC;IAAE,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IAAC,OAAO,EAAE,CAAC,CAAC,SAAS,CAAC;IAAC,cAAc,EAAE,CAAC,CAAA;CAAE,GAAG,CAAC,CAAC,CAAC;AAExF,wBAAgB,QAAQ,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,EAC7C,cAAc,EAAE,CAAC,GAChB,CAAC,CAAC,SAAS,CAAC;IAAE,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IAAC,OAAO,EAAE,CAAC,CAAC,SAAS,CAAC;IAAC,cAAc,EAAE,CAAC,CAAA;CAAE,CAAC,CAAC;AAUpF;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,CAAC,CAAC,WAAW,EAC9C,MAAM,EAAE,CAAC,GACR,CAAC,CAAC,SAAS,CAAC;IAAE,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IAAC,OAAO,EAAE,CAAC,CAAC,SAAS,CAAC;IAAC,YAAY,EAAE,CAAC,CAAC,SAAS,CAAA;CAAE,GAAG,CAAC,CAAC,CAAC;AAEhG,wBAAgB,QAAQ,IAAI,CAAC,CAAC,SAAS,CAAC;IACtC,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IACvB,OAAO,EAAE,CAAC,CAAC,SAAS,CAAC;IACrB,YAAY,EAAE,CAAC,CAAC,SAAS,CAAC;CAC3B,CAAC,CAAC"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tool-class.ts — The `Tool` orchestrator for code-first-agents deterministic tools.
|
|
3
|
+
*
|
|
4
|
+
* Implements the tool contract defined in the code-first agents pattern:
|
|
5
|
+
* - Subcommand dispatch with typed input/output Zod schemas
|
|
6
|
+
* - Validated JSON output (the shape IS the validator)
|
|
7
|
+
* - Self-describing `schema` subcommand (auto-registered)
|
|
8
|
+
* - `help` subcommand + unknown-subcommand fallback that exposes input schemas
|
|
9
|
+
* so LLM callers can self-correct
|
|
10
|
+
* - Always exits with code 0; errors communicated via `ok: false` envelope
|
|
11
|
+
*
|
|
12
|
+
* Spec: https://beogip.github.io/code-first-agents/patterns/deterministic-tools.html
|
|
13
|
+
*
|
|
14
|
+
* ## Usage
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { z } from "zod";
|
|
18
|
+
* import { Tool, l2Output } from "@code-first-agents/tool";
|
|
19
|
+
*
|
|
20
|
+
* new Tool({ name: "level-classifier", description: "..." })
|
|
21
|
+
* .subcommand({
|
|
22
|
+
* name: "classify",
|
|
23
|
+
* description: "Classify a SKILL.md by code-first level",
|
|
24
|
+
* input: z.object({ path: z.string() }).strict(),
|
|
25
|
+
* output: l2Output(z.enum(["L1", "L2", "L3"])),
|
|
26
|
+
* // Handler returns message + data. `ok: true` is framework-added.
|
|
27
|
+
* handler: ({ path }) => ({
|
|
28
|
+
* message: `classified ${path}`,
|
|
29
|
+
* classification: "L2",
|
|
30
|
+
* }),
|
|
31
|
+
* })
|
|
32
|
+
* .run(process.argv.slice(2));
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* ## Output envelope contract
|
|
36
|
+
*
|
|
37
|
+
* Handlers return the output shape **without `ok`** — the base class stamps
|
|
38
|
+
* `ok: true` onto the handler's result before validating against the output
|
|
39
|
+
* schema. This keeps handlers focused on `message` + business data and avoids
|
|
40
|
+
* TypeScript widening `ok: true` to `boolean` in object-literal returns.
|
|
41
|
+
*
|
|
42
|
+
* Use the level helpers (`l1Output`, `l2Output`, `l3Output` from
|
|
43
|
+
* `./output-helpers.ts`) to get the envelope + level-specific required
|
|
44
|
+
* fields baked in; fall back to raw `z.object({...})` when the tool's shape
|
|
45
|
+
* doesn't fit a level (remember to include `ok: z.literal(true)` and
|
|
46
|
+
* `message: z.string()` in the raw schema so validation covers the whole
|
|
47
|
+
* envelope).
|
|
48
|
+
*
|
|
49
|
+
* Error envelopes (`schema_violation`, `input_validation_error`,
|
|
50
|
+
* `unknown_subcommand`, `unexpected_error`) are class-authored — see
|
|
51
|
+
* `./envelopes.ts`.
|
|
52
|
+
*
|
|
53
|
+
* @module code-first-agents-tool/tool-class
|
|
54
|
+
*/
|
|
55
|
+
import { z } from "zod";
|
|
56
|
+
import type { SubcommandSpec, ToolMeta } from "./types.ts";
|
|
57
|
+
import { type ToolOutput } from "./utils.ts";
|
|
58
|
+
/**
|
|
59
|
+
* Orchestrator for a code-first deterministic tool. Register subcommands via
|
|
60
|
+
* {@link Tool.subcommand}, then call {@link Tool.run} with `process.argv.slice(2)`.
|
|
61
|
+
*
|
|
62
|
+
* The class always terminates the process with exit code 0 via `jsonOutput`;
|
|
63
|
+
* errors are communicated through the JSON envelope's `ok: false` + `error`
|
|
64
|
+
* fields.
|
|
65
|
+
*
|
|
66
|
+
* Reserved subcommand names `schema` and `help` are auto-registered by the
|
|
67
|
+
* class — attempting to register them manually throws `RangeError`.
|
|
68
|
+
*/
|
|
69
|
+
export declare class Tool {
|
|
70
|
+
private readonly meta;
|
|
71
|
+
private readonly subs;
|
|
72
|
+
/**
|
|
73
|
+
* @param meta - Tool metadata (name + description).
|
|
74
|
+
*/
|
|
75
|
+
constructor(meta: ToolMeta);
|
|
76
|
+
/**
|
|
77
|
+
* Register a subcommand. Chainable (returns `this`).
|
|
78
|
+
*
|
|
79
|
+
* Throws synchronously on:
|
|
80
|
+
* - reserved name collision (`schema`, `help`) → `RangeError`
|
|
81
|
+
* - duplicate name → `RangeError`
|
|
82
|
+
* - missing `input` schema → `TypeError`
|
|
83
|
+
* - missing `output` schema → `TypeError`
|
|
84
|
+
*
|
|
85
|
+
* @param spec - {@link SubcommandSpec} carrying name, description, input/output Zod schemas, and handler.
|
|
86
|
+
* @returns This tool instance, for chaining.
|
|
87
|
+
*/
|
|
88
|
+
subcommand<I extends z.ZodTypeAny, O extends z.ZodTypeAny>(spec: SubcommandSpec<I, O>): this;
|
|
89
|
+
/**
|
|
90
|
+
* Dispatch a subcommand from parsed `argv`. Always terminates the process
|
|
91
|
+
* via `jsonOutput` with exit code 0.
|
|
92
|
+
*
|
|
93
|
+
* Pure dispatch — each branch delegates to a single-responsibility helper:
|
|
94
|
+
* 1. {@link Tool.dispatchBuiltin} handles `schema` / `help`.
|
|
95
|
+
* 2. {@link Tool.rejectUnknown} handles empty or unregistered subcommands.
|
|
96
|
+
* 3. {@link Tool.runSubcommand} runs the validate → handler → validate pipeline.
|
|
97
|
+
* 4. Outer try/catch: {@link ToolError} → handler-emitted envelope with the
|
|
98
|
+
* custom code; anything else → `unexpected_error`.
|
|
99
|
+
*
|
|
100
|
+
* @param argv - CLI tokens, typically `process.argv.slice(2)`.
|
|
101
|
+
* @returns Never — the process exits via `jsonOutput`.
|
|
102
|
+
*/
|
|
103
|
+
run(argv: string[]): Promise<never>;
|
|
104
|
+
/**
|
|
105
|
+
* Pre-flight checks for a subcommand registration. Called by
|
|
106
|
+
* {@link Tool.subcommand}; throws synchronously on any violation.
|
|
107
|
+
*
|
|
108
|
+
* @param spec - The {@link SubcommandSpec} to validate.
|
|
109
|
+
* @throws `RangeError` for reserved-name collision or duplicate registration.
|
|
110
|
+
* @throws `TypeError` when `input` is missing.
|
|
111
|
+
*/
|
|
112
|
+
private validateRegistration;
|
|
113
|
+
/**
|
|
114
|
+
* Handle the auto-registered `schema` and `help` subcommands. Returns the
|
|
115
|
+
* success envelope for those names, or `null` to signal the caller should
|
|
116
|
+
* fall through to user-registered dispatch.
|
|
117
|
+
*
|
|
118
|
+
* @param name - The parsed subcommand name.
|
|
119
|
+
* @returns A success envelope for `schema`/`help`, or `null` otherwise.
|
|
120
|
+
*/
|
|
121
|
+
private dispatchBuiltin;
|
|
122
|
+
/**
|
|
123
|
+
* Handle empty argv and unregistered-subcommand cases. Returns the
|
|
124
|
+
* `unknown_subcommand` envelope (carrying the help listing for LLM
|
|
125
|
+
* self-correction) when applicable, or `null` when the subcommand is
|
|
126
|
+
* registered and should proceed.
|
|
127
|
+
*
|
|
128
|
+
* @param name - The parsed subcommand name.
|
|
129
|
+
* @returns An `unknown_subcommand` envelope, or `null` if the name is registered.
|
|
130
|
+
*/
|
|
131
|
+
private rejectUnknown;
|
|
132
|
+
/**
|
|
133
|
+
* Execute a registered subcommand: validate input → `await` handler →
|
|
134
|
+
* stamp `ok: true` → validate output. Returns whichever envelope the
|
|
135
|
+
* pipeline produced (success or one of the validation errors).
|
|
136
|
+
*
|
|
137
|
+
* Preconditions: `name` must be a registered subcommand name. Callers
|
|
138
|
+
* should run {@link Tool.rejectUnknown} first.
|
|
139
|
+
*
|
|
140
|
+
* @param name - The subcommand to execute.
|
|
141
|
+
* @param parsed - Raw parsed args from {@link parseArgs}.
|
|
142
|
+
* @returns A success envelope or a typed error envelope.
|
|
143
|
+
*/
|
|
144
|
+
private runSubcommand;
|
|
145
|
+
/**
|
|
146
|
+
* Programmatic entry point — runs a subcommand in-process without CLI
|
|
147
|
+
* arg parsing or `process.exit`. Identical validation pipeline to
|
|
148
|
+
* {@link Tool.run}, but accepts a plain object instead of argv tokens.
|
|
149
|
+
*
|
|
150
|
+
* @param subcommand - The subcommand name to dispatch.
|
|
151
|
+
* @param args - Input data as a plain object (bypasses CLI parsing).
|
|
152
|
+
* @returns The output envelope (success or error).
|
|
153
|
+
*
|
|
154
|
+
* Unlike `run()`, this method does NOT promote global flags (e.g. `--path`)
|
|
155
|
+
* into subcommand args. Callers must pass the full args object directly.
|
|
156
|
+
* Also, stderr output from handlers is not capturable — spawn the tool
|
|
157
|
+
* as a subprocess in tests that need to assert on stderr content.
|
|
158
|
+
*/
|
|
159
|
+
invoke(subcommand: string, args?: Record<string, unknown>): Promise<ToolOutput>;
|
|
160
|
+
private validateOutput;
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=tool-class.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-class.d.ts","sourceRoot":"","sources":["../src/tool-class.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAYxB,OAAO,KAAK,EAAc,cAAc,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACvE,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AASzD;;;;;;;;;;GAUG;AACH,qBAAa,IAAI;IACf,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAW;IAChC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAmC;IAExD;;OAEG;gBACS,IAAI,EAAE,QAAQ;IAI1B;;;;;;;;;;;OAWG;IACH,UAAU,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,EAAE,CAAC,SAAS,CAAC,CAAC,UAAU,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI;IAM5F;;;;;;;;;;;;;OAaG;IACG,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC;IAczC;;;;;;;OAOG;IACH,OAAO,CAAC,oBAAoB;IAuB5B;;;;;;;OAOG;IACH,OAAO,CAAC,eAAe;IAsBvB;;;;;;;;OAQG;IACH,OAAO,CAAC,aAAa;IAOrB;;;;;;;;;;;OAWG;YACW,aAAa;IAkB3B;;;;;;;;;;;;;OAaG;IACG,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IA2BzF,OAAO,CAAC,cAAc;CA4BvB"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* types.ts — Public interfaces for the code-first-agents Tool base class.
|
|
3
|
+
*
|
|
4
|
+
* Kept pure: no imports beyond `zod`, no runtime logic. Consumers of the
|
|
5
|
+
* `Tool` class type their code against these.
|
|
6
|
+
*
|
|
7
|
+
* @module code-first-agents-tool/types
|
|
8
|
+
*/
|
|
9
|
+
import type { z } from "zod";
|
|
10
|
+
/** Metadata describing the tool itself. Used in help output. */
|
|
11
|
+
export interface ToolMeta {
|
|
12
|
+
/** Tool identifier (usually matches the filename stem). */
|
|
13
|
+
name: string;
|
|
14
|
+
/** One-line human description shown in help output. */
|
|
15
|
+
description: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Raw CLI args after parsing. Flags are `--key value` pairs; bare `--flag`
|
|
19
|
+
* (with no following value) resolves to `true`. Positional args are any
|
|
20
|
+
* tokens that don't start with `--`.
|
|
21
|
+
*/
|
|
22
|
+
export interface ParsedArgs {
|
|
23
|
+
/** Flag key → value mapping. Last-one-wins on repeated keys. */
|
|
24
|
+
flags: Record<string, string | true>;
|
|
25
|
+
/** Positional (non-flag) tokens in order. */
|
|
26
|
+
positional: string[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* What a handler is expected to return: the output shape **without** `ok`.
|
|
30
|
+
* The base class stamps `ok: true` onto the result before output validation,
|
|
31
|
+
* so handlers stay focused on `message` + business data. This also avoids
|
|
32
|
+
* TypeScript widening `ok: true` to `boolean` in object-literal returns.
|
|
33
|
+
*/
|
|
34
|
+
export type HandlerReturn<O extends z.ZodTypeAny> = Omit<z.infer<O>, "ok">;
|
|
35
|
+
/**
|
|
36
|
+
* A registered subcommand spec. Both `input` and `output` Zod schemas are
|
|
37
|
+
* required — they validate CLI args before the handler runs and the
|
|
38
|
+
* handler's return value before emit.
|
|
39
|
+
*
|
|
40
|
+
* @typeParam I - Input Zod schema type
|
|
41
|
+
* @typeParam O - Output Zod schema type
|
|
42
|
+
*/
|
|
43
|
+
export interface SubcommandSpec<I extends z.ZodTypeAny, O extends z.ZodTypeAny> {
|
|
44
|
+
/** Subcommand name as it appears on the CLI (e.g. "classify"). */
|
|
45
|
+
name: string;
|
|
46
|
+
/** One-line description shown in help output. */
|
|
47
|
+
description: string;
|
|
48
|
+
/**
|
|
49
|
+
* Zod schema for the validated flags + positional args. Positional args
|
|
50
|
+
* are exposed under a reserved `_` key when present. Use `.strict()` to
|
|
51
|
+
* reject unknown flags loudly.
|
|
52
|
+
*
|
|
53
|
+
* **Sync only:** the base class uses `safeParse` (not `safeParseAsync`).
|
|
54
|
+
* Schemas with `.refine(async ...)` or `.transform(async ...)` will cause
|
|
55
|
+
* a hard runtime throw at dispatch time (caught by the outer `unexpected_error`
|
|
56
|
+
* envelope, but with a cryptic message). Use synchronous validators only.
|
|
57
|
+
*/
|
|
58
|
+
input: I;
|
|
59
|
+
/**
|
|
60
|
+
* Zod schema for the full output envelope (including `ok: z.literal(true)`
|
|
61
|
+
* and `message: z.string()`). Use the level helpers `l1Output` / `l2Output`
|
|
62
|
+
* / `l3Output` to get the envelope baked in, or pass a raw `z.object`.
|
|
63
|
+
* The handler returns everything **except** `ok`; the base class adds it.
|
|
64
|
+
*
|
|
65
|
+
* **Sync only:** same constraint as `input` — no async transforms or refinements.
|
|
66
|
+
*/
|
|
67
|
+
output: O;
|
|
68
|
+
/**
|
|
69
|
+
* Business logic. Receives the validated, typed input. Returns the output
|
|
70
|
+
* shape **without** `ok` — the base class adds `ok: true` before validating
|
|
71
|
+
* against the output schema. May return a plain value or a Promise.
|
|
72
|
+
*/
|
|
73
|
+
handler: (args: z.infer<I>) => HandlerReturn<O> | Promise<HandlerReturn<O>>;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,gEAAgE;AAChE,MAAM,WAAW,QAAQ;IACvB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;IACrC,6CAA6C;IAC7C,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;;;;GAKG;AACH,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;AAE3E;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,EAAE,CAAC,SAAS,CAAC,CAAC,UAAU;IAC5E,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;;;;;OASG;IACH,KAAK,EAAE,CAAC,CAAC;IACT;;;;;;;OAOG;IACH,MAAM,EAAE,CAAC,CAAC;IACV;;;;OAIG;IACH,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,aAAa,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;CAC7E"}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Standard JSON envelope for all tool stdout output. */
|
|
2
|
+
export type ToolOutput = {
|
|
3
|
+
ok: boolean;
|
|
4
|
+
message: string;
|
|
5
|
+
[key: string]: unknown;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Print a {@link ToolOutput} as JSON to stdout and terminate with exit code 0.
|
|
9
|
+
* @param data - The tool output envelope to serialize.
|
|
10
|
+
* @returns Never — the process exits.
|
|
11
|
+
*/
|
|
12
|
+
export declare function jsonOutput(data: ToolOutput): never;
|
|
13
|
+
/**
|
|
14
|
+
* Extract a human-readable message from an unknown thrown value. Preserves
|
|
15
|
+
* the `Error.message` when available; otherwise coerces via `String(...)`.
|
|
16
|
+
* @param err - The caught value (Error instance, string, null, etc.).
|
|
17
|
+
* @returns A non-undefined string suitable for envelope `message` / log output.
|
|
18
|
+
*/
|
|
19
|
+
export declare function stringifyError(err: unknown): string;
|
|
20
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,MAAM,MAAM,UAAU,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AAElF;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,KAAK,CAIlD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAEnD"}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@code-first-agents/tool",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Code-first agent tool definitions with Zod schemas",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/beogip/code-first-agents-tool.git"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"dev": "bun run --watch src/index.ts",
|
|
27
|
+
"build": "rm -rf dist && bun build src/index.ts --outdir dist --format esm --target node --packages external && tsc -p tsconfig.build.json",
|
|
28
|
+
"test": "bun test",
|
|
29
|
+
"lint": "biome lint .",
|
|
30
|
+
"format": "biome format --write .",
|
|
31
|
+
"check": "biome check --write .",
|
|
32
|
+
"prepare": "test -d .git && lefthook install || true",
|
|
33
|
+
"prepublishOnly": "bun run build"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"zod": "^4.0.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"zod": "^4.0.0",
|
|
40
|
+
"@biomejs/biome": "latest",
|
|
41
|
+
"bun-types": "latest",
|
|
42
|
+
"lefthook": "latest",
|
|
43
|
+
"semantic-release": "latest",
|
|
44
|
+
"@semantic-release/changelog": "latest",
|
|
45
|
+
"@semantic-release/git": "latest",
|
|
46
|
+
"@semantic-release/github": "latest",
|
|
47
|
+
"@semantic-release/npm": "latest",
|
|
48
|
+
"typescript": "latest"
|
|
49
|
+
}
|
|
50
|
+
}
|