@@ -8,6 +8,8 @@ import process from 'node:process';
88
99import { DAEMON_CLIENT_NAME } from '../daemon/utils.js' ;
1010import { logger } from '../logger.js' ;
11+ import type { zod } from '../third_party/index.js' ;
12+ import type { ShapeOutput } from '../third_party/index.js' ;
1113
1214import type { LocalState , Persistence } from './persistence.js' ;
1315import { FilePersistence } from './persistence.js' ;
@@ -20,6 +22,108 @@ import {
2022import { WatchdogClient } from './WatchdogClient.js' ;
2123
2224const MS_PER_DAY = 24 * 60 * 60 * 1000 ;
25+ const PARAM_BLOCKLIST = new Set ( [ 'uid' ] ) ;
26+
27+ const SUPPORTED_ZOD_TYPES = [
28+ 'ZodString' ,
29+ 'ZodNumber' ,
30+ 'ZodBoolean' ,
31+ 'ZodArray' ,
32+ 'ZodEnum' ,
33+ ] as const ;
34+ type ZodType = ( typeof SUPPORTED_ZOD_TYPES ) [ number ] ;
35+
36+ function isZodType ( type : string ) : type is ZodType {
37+ return SUPPORTED_ZOD_TYPES . includes ( type as ZodType ) ;
38+ }
39+
40+ function getZodType ( zodType : zod . ZodTypeAny ) : ZodType {
41+ const def = zodType . _def ;
42+ const typeName = def . typeName ;
43+
44+ if (
45+ typeName === 'ZodOptional' ||
46+ typeName === 'ZodDefault' ||
47+ typeName === 'ZodNullable'
48+ ) {
49+ return getZodType ( def . innerType ) ;
50+ }
51+ if ( typeName === 'ZodEffects' ) {
52+ return getZodType ( def . schema ) ;
53+ }
54+
55+ if ( isZodType ( typeName ) ) {
56+ return typeName ;
57+ }
58+ throw new Error ( `Unsupported zod type for tool parameter: ${ typeName } ` ) ;
59+ }
60+
61+ type LoggedToolCallArgValue = string | number | boolean ;
62+
63+ function transformName ( zodType : ZodType , name : string ) : string {
64+ if ( zodType === 'ZodString' ) {
65+ return `${ name } _length` ;
66+ } else if ( zodType === 'ZodArray' ) {
67+ return `${ name } _count` ;
68+ } else {
69+ return name ;
70+ }
71+ }
72+
73+ function transformValue (
74+ zodType : ZodType ,
75+ value : unknown ,
76+ ) : LoggedToolCallArgValue {
77+ if ( zodType === 'ZodString' ) {
78+ return ( value as string ) . length ;
79+ } else if ( zodType === 'ZodArray' ) {
80+ return ( value as unknown [ ] ) . length ;
81+ } else {
82+ return value as LoggedToolCallArgValue ;
83+ }
84+ }
85+
86+ function hasEquivalentType ( zodType : ZodType , value : unknown ) : boolean {
87+ if ( zodType === 'ZodString' ) {
88+ return typeof value === 'string' ;
89+ } else if ( zodType === 'ZodArray' ) {
90+ return Array . isArray ( value ) ;
91+ } else if ( zodType === 'ZodNumber' ) {
92+ return typeof value === 'number' ;
93+ } else if ( zodType === 'ZodBoolean' ) {
94+ return typeof value === 'boolean' ;
95+ } else if ( zodType === 'ZodEnum' ) {
96+ return (
97+ typeof value === 'string' ||
98+ typeof value === 'number' ||
99+ typeof value === 'boolean'
100+ ) ;
101+ } else {
102+ return false ;
103+ }
104+ }
105+
106+ export function sanitizeParams (
107+ params : ShapeOutput < zod . ZodRawShape > ,
108+ schema : zod . ZodRawShape ,
109+ ) : ShapeOutput < zod . ZodRawShape > {
110+ const transformed : ShapeOutput < zod . ZodRawShape > = { } ;
111+ for ( const [ name , value ] of Object . entries ( params ) ) {
112+ if ( PARAM_BLOCKLIST . has ( name ) ) {
113+ continue ;
114+ }
115+ const zodType = getZodType ( schema [ name ] ) ;
116+ if ( ! hasEquivalentType ( zodType , value ) ) {
117+ throw new Error (
118+ `parameter ${ name } has type ${ zodType } but value ${ value } is not of equivalent type` ,
119+ ) ;
120+ }
121+ const transformedName = transformName ( zodType , name ) ;
122+ const transformedValue = transformValue ( zodType , value ) ;
123+ transformed [ transformedName ] = transformedValue ;
124+ }
125+ return transformed ;
126+ }
23127
24128function detectOsType ( ) : OsType {
25129 switch ( process . platform ) {
0 commit comments