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