@@ -2861,15 +2861,22 @@ async function applyPrepareMessages(
28612861}
28622862
28632863/**
2864- * Resolve the `tools` option for the current turn and cache it in locals so
2864+ * Resolve the `tools` option into a concrete `ToolSet` and cache it in locals so
28652865 * `toModelMessages` can pass it to `convertToModelMessages`. For the function
2866- * form, invokes the user function once per turn with the current turn context
2867- * (which already has parsed `clientData`). No-op when no `tools` were declared.
2868- * A throwing tools function logs and leaves the previously-resolved value in
2869- * place rather than killing the turn.
2866+ * form, invokes the user function with the given context (or the current turn
2867+ * context when no override is passed). Pass an `override` for the boot-time
2868+ * history conversion, which runs before the per-turn context exists and uses
2869+ * the run/continuation payload's `clientData`.
2870+ *
2871+ * Fails closed: a throwing resolver propagates rather than carrying a prior
2872+ * turn's set forward. The function form can gate capabilities by user or flag,
2873+ * so reusing stale tools would leak capabilities. No-op when no `tools` were
2874+ * declared.
28702875 * @internal
28712876 */
2872- async function resolveTurnTools ( ) : Promise < void > {
2877+ async function resolveTurnTools (
2878+ override ?: { chatId : string ; turn : number ; continuation : boolean ; clientData : unknown }
2879+ ) : Promise < void > {
28732880 const option = locals . get ( chatToolsOptionKey ) ;
28742881 if ( ! option ) return ;
28752882
@@ -2878,20 +2885,14 @@ async function resolveTurnTools(): Promise<void> {
28782885 return ;
28792886 }
28802887
2881- const turnCtx = locals . get ( chatTurnContextKey ) ;
2882- try {
2883- const resolved = await option ( {
2884- chatId : turnCtx ?. chatId ?? "" ,
2885- turn : turnCtx ?. turn ?? 0 ,
2886- continuation : turnCtx ?. continuation ?? false ,
2887- clientData : turnCtx ?. clientData ,
2888- } ) ;
2889- locals . set ( chatResolvedToolsKey , resolved ) ;
2890- } catch ( error ) {
2891- logger . warn ( "chat.agent: tools() resolver threw; reusing previous tools for this turn" , {
2892- error : error instanceof Error ? error . message : String ( error ) ,
2893- } ) ;
2894- }
2888+ const ctx = override ?? locals . get ( chatTurnContextKey ) ;
2889+ const resolved = await option ( {
2890+ chatId : ctx ?. chatId ?? "" ,
2891+ turn : ctx ?. turn ?? 0 ,
2892+ continuation : ctx ?. continuation ?? false ,
2893+ clientData : ctx ?. clientData ,
2894+ } ) ;
2895+ locals . set ( chatResolvedToolsKey , resolved ) ;
28952896}
28962897
28972898/**
@@ -5194,9 +5195,9 @@ function chatAgent<
51945195 | ToolSet
51955196 | ( ( event : ResolveToolsEvent < unknown > ) => ToolSet | Promise < ToolSet > )
51965197 ) ;
5197- // Static tools are usable immediately (e.g. the boot-time history
5198- // seed before the first turn context exists). The function form is
5199- // resolved per-turn once `clientData` is parsed (see resolveTurnTools).
5198+ // Static tools are usable immediately. The function form is resolved
5199+ // just before the boot history conversion (with the payload's
5200+ // clientData) and again per-turn (see resolveTurnTools).
52005201 if ( typeof toolsOption !== "function" ) {
52015202 locals . set ( chatResolvedToolsKey , toolsOption ) ;
52025203 }
@@ -5591,6 +5592,29 @@ function chatAgent<
55915592 }
55925593
55935594 if ( accumulatedUIMessages . length > 0 ) {
5595+ // Resolve a function-form `tools` with the run/continuation payload's
5596+ // clientData so this conversion of the restored history applies each
5597+ // tool's toModelOutput (static tools were already seeded above). This
5598+ // only re-renders saved history, so it fails open: a resolver hiccup
5599+ // logs and converts without tools rather than blocking the resume.
5600+ // Per-turn resolveTurnTools still fails closed for live turns.
5601+ if ( typeof toolsOption === "function" ) {
5602+ try {
5603+ await resolveTurnTools ( {
5604+ chatId : payload . chatId ,
5605+ turn : 0 ,
5606+ continuation : payload . continuation ?? false ,
5607+ clientData : parseClientData
5608+ ? await parseClientData ( payload . metadata )
5609+ : payload . metadata ,
5610+ } ) ;
5611+ } catch ( error ) {
5612+ logger . warn (
5613+ "chat.agent: tools() resolver threw at boot; restored history converted without toModelOutput" ,
5614+ { error : error instanceof Error ? error . message : String ( error ) }
5615+ ) ;
5616+ }
5617+ }
55945618 try {
55955619 accumulatedMessages = await toModelMessages ( accumulatedUIMessages ) ;
55965620 } catch ( error ) {
0 commit comments