11import type * as models from '../models/index.js' ;
22import type { NextTurnParamsContext , ParsedToolCall , Tool } from './tool-types.js' ;
33
4+ /**
5+ * Type guard to check if a value is a Record<string, unknown>
6+ */
7+ function isRecord ( value : unknown ) : value is Record < string , unknown > {
8+ return typeof value === 'object' && value !== null && ! Array . isArray ( value ) ;
9+ }
10+
411/**
512 * Build a NextTurnParamsContext from the current request
613 * Extracts relevant fields that can be modified by nextTurnParams functions
@@ -40,13 +47,10 @@ export async function executeNextTurnParamsFunctions(
4047 // Build initial context from current request
4148 const context = buildNextTurnParamsContext ( currentRequest ) ;
4249
43- // Group tool calls by parameter they modify
44- const paramFunctions = new Map <
45- keyof NextTurnParamsContext ,
46- Array < { params : Record < string , unknown > ; fn : ( params : Record < string , unknown > , context : NextTurnParamsContext ) => unknown } >
47- > ( ) ;
48-
4950 // Collect all nextTurnParams functions from tools (in tools array order)
51+ const result : Partial < NextTurnParamsContext > = { } ;
52+ let workingContext = { ...context } ;
53+
5054 for ( const tool of tools ) {
5155 if ( ! tool . function . nextTurnParams ) {
5256 continue ;
@@ -57,41 +61,83 @@ export async function executeNextTurnParamsFunctions(
5761
5862 for ( const call of callsForTool ) {
5963 // For each parameter function in this tool's nextTurnParams
60- for ( const [ paramKey , fn ] of Object . entries ( tool . function . nextTurnParams ) ) {
61- if ( ! paramFunctions . has ( paramKey as keyof NextTurnParamsContext ) ) {
62- paramFunctions . set ( paramKey as keyof NextTurnParamsContext , [ ] ) ;
63- }
64- paramFunctions . get ( paramKey as keyof NextTurnParamsContext ) ! . push ( {
65- params : call . arguments as Record < string , unknown > ,
66- fn : fn as ( params : Record < string , unknown > , context : NextTurnParamsContext ) => unknown ,
67- } ) ;
64+ // We need to process each key individually to maintain type safety
65+ const nextParams = tool . function . nextTurnParams ;
66+
67+ // Validate that call.arguments is a record using type guard
68+ if ( ! isRecord ( call . arguments ) ) {
69+ throw new Error (
70+ `Tool call arguments for ${ tool . function . name } must be an object, got ${ typeof call . arguments } `
71+ ) ;
6872 }
73+
74+ // Process each parameter key with proper typing
75+ await processNextTurnParamsForCall ( nextParams , call . arguments , workingContext , result ) ;
6976 }
7077 }
7178
72- // Compose and execute functions for each parameter
73- const result : Partial < NextTurnParamsContext > = { } ;
74- let workingContext = { ...context } ;
79+ return result ;
80+ }
7581
76- for ( const [ paramKey , functions ] of paramFunctions . entries ( ) ) {
77- // Compose all functions for this parameter
78- let currentValue = workingContext [ paramKey ] ;
82+ /**
83+ * Process nextTurnParams for a single tool call with full type safety
84+ */
85+ async function processNextTurnParamsForCall (
86+ nextParams : Record < string , unknown > ,
87+ params : Record < string , unknown > ,
88+ workingContext : NextTurnParamsContext ,
89+ result : Partial < NextTurnParamsContext >
90+ ) : Promise < void > {
91+ // Type-safe processing for each known parameter key
92+ // We iterate through keys and use runtime checks instead of casts
93+ for ( const paramKey of Object . keys ( nextParams ) ) {
94+ const fn = nextParams [ paramKey ] ;
7995
80- for ( const { params , fn } of functions ) {
81- // Update context with current value
82- workingContext = { ... workingContext , [ paramKey ] : currentValue } ;
96+ if ( typeof fn !== 'function' ) {
97+ continue ;
98+ }
8399
84- // Execute function with composition
85- // Type assertion needed because fn returns unknown but we know it returns the correct type
86- currentValue = await Promise . resolve ( fn ( params , workingContext ) ) as typeof currentValue ;
100+ // Validate that paramKey is actually a key of NextTurnParamsContext
101+ if ( ! isValidNextTurnParamKey ( paramKey ) ) {
102+ // Skip invalid keys silently - they're not part of the API
103+ continue ;
87104 }
88105
89- // TypeScript can't infer that paramKey corresponds to the correct value type
90- // so we use a type assertion here
91- ( result as any ) [ paramKey ] = currentValue ;
106+ // Execute the function and await the result
107+ const newValue = await Promise . resolve ( fn ( params , workingContext ) ) ;
108+
109+ // Update the result using type-safe assignment
110+ setNextTurnParam ( result , paramKey , newValue ) ;
92111 }
112+ }
93113
94- return result ;
114+ /**
115+ * Type guard to check if a string is a valid NextTurnParamsContext key
116+ */
117+ function isValidNextTurnParamKey ( key : string ) : key is keyof NextTurnParamsContext {
118+ const validKeys : ReadonlySet < string > = new Set ( [
119+ 'input' ,
120+ 'model' ,
121+ 'models' ,
122+ 'temperature' ,
123+ 'maxOutputTokens' ,
124+ 'topP' ,
125+ 'topK' ,
126+ 'instructions' ,
127+ ] ) ;
128+ return validKeys . has ( key ) ;
129+ }
130+
131+ /**
132+ * Type-safe setter for NextTurnParamsContext
133+ * Ensures the value type matches the key type
134+ */
135+ function setNextTurnParam < K extends keyof NextTurnParamsContext > (
136+ target : Partial < NextTurnParamsContext > ,
137+ key : K ,
138+ value : NextTurnParamsContext [ K ]
139+ ) : void {
140+ target [ key ] = value ;
95141}
96142
97143/**
0 commit comments