Skip to content

Commit 0288fb1

Browse files
mattappersonclaude
andcommitted
fix: improve type inference for tools without outputSchema
- Split RegularToolConfig into two types: with and without outputSchema - Tools without outputSchema now correctly infer return type from execute function - Fixes 'result' is of type 'unknown' error in tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 81fa1dd commit 0288fb1

File tree

1 file changed

+49
-13
lines changed

1 file changed

+49
-13
lines changed

src/lib/create-tool.ts

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,41 @@ import {
88
} from "./tool-types.js";
99

1010
/**
11-
* Configuration for a regular tool (without eventSchema)
11+
* Configuration for a regular tool with outputSchema
1212
*/
13-
type RegularToolConfig<
13+
type RegularToolConfigWithOutput<
1414
TInput extends ZodObject<ZodRawShape>,
15-
TOutput extends ZodType = ZodType<unknown>,
15+
TOutput extends ZodType,
1616
> = {
1717
name: string;
1818
description?: string;
1919
inputSchema: TInput;
20-
outputSchema?: TOutput;
20+
outputSchema: TOutput;
2121
eventSchema?: undefined;
2222
execute: (
2323
params: z.infer<TInput>,
2424
context?: TurnContext
2525
) => Promise<z.infer<TOutput>> | z.infer<TOutput>;
2626
};
2727

28+
/**
29+
* Configuration for a regular tool without outputSchema (infers return type from execute)
30+
*/
31+
type RegularToolConfigWithoutOutput<
32+
TInput extends ZodObject<ZodRawShape>,
33+
TReturn,
34+
> = {
35+
name: string;
36+
description?: string;
37+
inputSchema: TInput;
38+
outputSchema?: undefined;
39+
eventSchema?: undefined;
40+
execute: (
41+
params: z.infer<TInput>,
42+
context?: TurnContext
43+
) => Promise<TReturn> | TReturn;
44+
};
45+
2846
/**
2947
* Configuration for a generator tool (with eventSchema)
3048
*/
@@ -54,17 +72,25 @@ type ManualToolConfig<TInput extends ZodObject<ZodRawShape>> = {
5472
execute: false;
5573
};
5674

75+
/**
76+
* Union type for all regular tool configs
77+
*/
78+
type RegularToolConfig<TInput extends ZodObject<ZodRawShape>, TOutput extends ZodType, TReturn> =
79+
| RegularToolConfigWithOutput<TInput, TOutput>
80+
| RegularToolConfigWithoutOutput<TInput, TReturn>;
81+
5782
/**
5883
* Type guard to check if config is a generator tool config (has eventSchema)
5984
*/
6085
function isGeneratorConfig<
6186
TInput extends ZodObject<ZodRawShape>,
6287
TEvent extends ZodType,
6388
TOutput extends ZodType,
89+
TReturn,
6490
>(
6591
config:
6692
| GeneratorToolConfig<TInput, TEvent, TOutput>
67-
| RegularToolConfig<TInput, TOutput>
93+
| RegularToolConfig<TInput, TOutput, TReturn>
6894
| ManualToolConfig<TInput>
6995
): config is GeneratorToolConfig<TInput, TEvent, TOutput> {
7096
return "eventSchema" in config && config.eventSchema !== undefined;
@@ -73,10 +99,10 @@ function isGeneratorConfig<
7399
/**
74100
* Type guard to check if config is a manual tool config (execute === false)
75101
*/
76-
function isManualConfig<TInput extends ZodObject<ZodRawShape>>(
102+
function isManualConfig<TInput extends ZodObject<ZodRawShape>, TOutput extends ZodType, TReturn>(
77103
config:
78104
| GeneratorToolConfig<TInput, ZodType, ZodType>
79-
| RegularToolConfig<TInput, ZodType>
105+
| RegularToolConfig<TInput, TOutput, TReturn>
80106
| ManualToolConfig<TInput>
81107
): config is ManualToolConfig<TInput> {
82108
return config.execute === false;
@@ -141,25 +167,33 @@ export function tool<TInput extends ZodObject<ZodRawShape>>(
141167
config: ManualToolConfig<TInput>
142168
): ManualTool<TInput>;
143169

144-
// Overload for regular tools (no eventSchema)
170+
// Overload for regular tools with outputSchema
145171
export function tool<
146172
TInput extends ZodObject<ZodRawShape>,
147-
TOutput extends ZodType = ZodType<unknown>,
148-
>(config: RegularToolConfig<TInput, TOutput>): ToolWithExecute<TInput, TOutput>;
173+
TOutput extends ZodType,
174+
>(config: RegularToolConfigWithOutput<TInput, TOutput>): ToolWithExecute<TInput, TOutput>;
175+
176+
// Overload for regular tools without outputSchema (infers return type)
177+
export function tool<
178+
TInput extends ZodObject<ZodRawShape>,
179+
TReturn,
180+
>(config: RegularToolConfigWithoutOutput<TInput, TReturn>): ToolWithExecute<TInput, ZodType<TReturn>>;
149181

150182
// Implementation
151183
export function tool<
152184
TInput extends ZodObject<ZodRawShape>,
153185
TEvent extends ZodType,
154186
TOutput extends ZodType,
187+
TReturn,
155188
>(
156189
config:
157190
| GeneratorToolConfig<TInput, TEvent, TOutput>
158-
| RegularToolConfig<TInput, TOutput>
191+
| RegularToolConfig<TInput, TOutput, TReturn>
159192
| ManualToolConfig<TInput>
160193
):
161194
| ToolWithGenerator<TInput, TEvent, TOutput>
162195
| ToolWithExecute<TInput, TOutput>
196+
| ToolWithExecute<TInput, ZodType<TReturn>>
163197
| ManualTool<TInput> {
164198
// Check for manual tool first (execute === false)
165199
if (isManualConfig(config)) {
@@ -205,11 +239,13 @@ export function tool<
205239
}
206240

207241
// Regular tool (has execute function, no eventSchema)
208-
const fn: ToolWithExecute<TInput, TOutput>["function"] = {
242+
// Type assertion needed because we have two overloads (with/without outputSchema)
243+
// and the implementation needs to handle both cases
244+
const fn = {
209245
name: config.name,
210246
inputSchema: config.inputSchema,
211247
execute: config.execute,
212-
};
248+
} as ToolWithExecute<TInput, TOutput>["function"];
213249

214250
if (config.description !== undefined) {
215251
fn.description = config.description;

0 commit comments

Comments
 (0)