feat(agent): pi-schema's Static types TypeBox-built schemas too, without depending on TypeBox

This commit is contained in:
Mario Zechner 2026-11-01 11:12:44 +00:00
parent a6bde34bb6
commit 6291b9bcc3
12 changed files with 304 additions and 10 deletions

View file

@ -4,7 +4,7 @@
### Breaking Changes
- `AgentTool` takes any JSON Schema object as `parameters` (`AgentTool<TParameters extends object>`) and types `params` and `prepareArguments` through `@earendil-works/pi-schema` (`AgentToolParams<TParameters>`): pi-schema's builders give their static type, other schemas (TypeBox included) give `unknown`, `AgentTool<any>` stays `any`. `outputSchema` is any JSON Schema object. `typebox` is no longer a dependency. Migration: build parameters with pi-schema (`obj({ path: str() })` instead of `Type.Object({ path: Type.String() })`), or keep TypeBox schemas and narrow `params` yourself.
- `AgentTool` takes any JSON Schema object as `parameters` (`AgentTool<TParameters extends object>`) and types `params` and `prepareArguments` through `@earendil-works/pi-schema` (`AgentToolParams<TParameters>`): pi-schema's builders give their static type, TypeBox schemas give TypeBox's static type for the common kinds (String, Number, Integer, Boolean, Null, Literal, Array, Object with optional and readonly properties, Union, Record, Unsafe, Unknown, Any; other kinds such as Enum, Tuple and Intersect give `unknown`), other schemas give `unknown`, `AgentTool<any>` stays `any`. `outputSchema` is any JSON Schema object. `typebox` is no longer a dependency. Migration: build parameters with pi-schema (`obj({ path: str() })` instead of `Type.Object({ path: Type.String() })`), or keep TypeBox schemas (narrow `params` yourself only where they use other kinds).
### Fixed

View file

@ -463,8 +463,8 @@ export interface AgentToolCallOutcome {
export type AgentToolUpdateCallback<T = any> = (partialResult: AgentToolResult<T>) => void;
/**
* The arguments type of a tool with parameter schema `TParameters`: `Static<TParameters>` for schemas built with
* `@earendil-works/pi-schema`'s builders, `unknown` for other schemas, and `any` for `AgentTool<any>`.
* The arguments type of a tool with parameter schema `TParameters`: pi-schema's `Static<TParameters>` (the builders'
* type, TypeBox's for the common TypeBox kinds, else `unknown`), and `any` for `AgentTool<any>`.
*/
export type AgentToolParams<TParameters> = IsAny<TParameters> extends true ? any : Static<TParameters>;

View file

@ -0,0 +1,65 @@
// AgentTool params typed from a TypeBox-built parameter schema. pi-agent-core does not depend on TypeBox: the schema
// below has TypeBox 1.x's type shapes as its d.mts declare them; pi-schema's own tests check the shapes against TypeBox.
import { describe, expectTypeOf, it } from "vitest";
import type { AgentTool } from "../src/types.ts";
interface TString {
"~kind": "String";
type: "string";
}
interface TLiteral<Value> {
"~kind": "Literal";
const: Value;
}
interface TUnion<Branches> {
"~kind": "Union";
anyOf: Branches;
}
interface TObject<Properties> {
"~kind": "Object";
type: "object";
properties: Properties;
required: string[];
}
type TOptional<Schema> = Schema & { "~optional": true };
const parameters: TObject<{ command: TString; mode: TOptional<TUnion<[TLiteral<"fast">, TLiteral<"deep">]>> }> = {
"~kind": "Object",
type: "object",
properties: {
command: { "~kind": "String", type: "string" },
mode: {
"~kind": "Union",
anyOf: [
{ "~kind": "Literal", const: "fast" },
{ "~kind": "Literal", const: "deep" },
],
"~optional": true,
},
},
required: ["command"],
};
type Params = { command: string; mode?: "fast" | "deep" };
describe("AgentTool params of a TypeBox schema", () => {
it("are typed by the schema in execute() and prepareArguments()", () => {
const tool: AgentTool<typeof parameters> = {
name: "bash",
label: "Bash",
description: "Runs a command",
parameters,
async execute(_toolCallId, params) {
expectTypeOf(params).toEqualTypeOf<Params>();
return { content: [], details: undefined };
},
};
expectTypeOf<Parameters<typeof tool.execute>[1]>().toEqualTypeOf<Params>();
expectTypeOf<ReturnType<NonNullable<typeof tool.prepareArguments>>>().toEqualTypeOf<Params>();
});
});