@@ -72,6 +72,8 @@ export type GenerationResult = {
7272 } ;
7373 /** Full per-tool call counts from this generation */
7474 toolCallsByName ?: Record < string , number > ;
75+ /** Per-tool call counts that should be used for credit charging */
76+ chargeableToolCallsByName ?: Record < string , number > ;
7577 /** Total number of tool calls from this generation */
7678 totalToolCalls ?: number ;
7779 /** Custom skills explicitly read via loadSkill() in this run */
@@ -140,6 +142,10 @@ const TOOL_METADATA_BY_DEFAULT_SKILL_NAME = new Map(
140142 AI_AGENT_TOOL_CATALOG . map ( ( entry ) => [ entry . defaultSkill . name , entry ] )
141143) ;
142144const LOAD_SKILL_TOOL_NAME = "loadSkill" ;
145+ const BUILT_IN_TOOL_NAME_SET = new Set < string > ( [
146+ ...AI_AGENT_TOOL_CATALOG . map ( ( t ) => t . id ) ,
147+ LOAD_SKILL_TOOL_NAME ,
148+ ] ) ;
143149
144150export type RuntimeCustomSkillCatalogEntry = {
145151 fileName : string ;
@@ -153,8 +159,21 @@ type ToolCallLike = {
153159 toolName ?: string ;
154160} ;
155161
162+ type ToolResultLike = {
163+ toolName ?: string ;
164+ output ?: unknown ;
165+ } ;
166+
167+ type ToolContentPartLike = {
168+ type ?: string ;
169+ toolName ?: string ;
170+ output ?: unknown ;
171+ } ;
172+
156173type ToolStepLike = {
157- toolCalls ?: ToolCallLike [ ] ;
174+ toolCalls ?: readonly ToolCallLike [ ] ;
175+ toolResults ?: readonly ToolResultLike [ ] ;
176+ content ?: readonly ToolContentPartLike [ ] ;
158177} ;
159178
160179function clampToolInvocationBudget (
@@ -191,7 +210,7 @@ export function getNonFinishToolCallCount(
191210}
192211
193212function countNonFinishToolCallsFromSteps (
194- steps : ToolStepLike [ ] | undefined
213+ steps : readonly ToolStepLike [ ] | undefined
195214) : number {
196215 if ( ! steps || steps . length === 0 ) {
197216 return 0 ;
@@ -213,6 +232,128 @@ function countNonFinishToolCallsFromSteps(
213232 return total ;
214233}
215234
235+ function isRecord ( value : unknown ) : value is Record < string , unknown > {
236+ return typeof value === "object" && value !== null && ! Array . isArray ( value ) ;
237+ }
238+
239+ function isBuiltInToolName ( toolName : string ) : boolean {
240+ return BUILT_IN_TOOL_NAME_SET . has ( toolName ) ;
241+ }
242+
243+ function incrementToolCount (
244+ counts : Record < string , number > ,
245+ toolName : string
246+ ) : void {
247+ counts [ toolName ] = ( counts [ toolName ] ?? 0 ) + 1 ;
248+ }
249+
250+ function outputIndicatesToolFailure ( output : unknown ) : boolean {
251+ if ( ! isRecord ( output ) ) {
252+ return false ;
253+ }
254+
255+ if ( "success" in output && output . success === false ) {
256+ return true ;
257+ }
258+
259+ return (
260+ "success" in output &&
261+ output . success !== true &&
262+ typeof output . error === "string" &&
263+ output . error . length > 0
264+ ) ;
265+ }
266+
267+ export function buildFailedBuiltInToolCallsByName (
268+ steps : readonly ToolStepLike [ ] | undefined
269+ ) : Record < string , number > {
270+ if ( ! steps || steps . length === 0 ) {
271+ return { } ;
272+ }
273+
274+ const failedByName : Record < string , number > = { } ;
275+
276+ for ( const step of steps ) {
277+ const contentParts = Array . isArray ( step . content ) ? step . content : [ ] ;
278+
279+ if ( contentParts . length > 0 ) {
280+ for ( const part of contentParts ) {
281+ const partType = part ?. type ;
282+ const toolName = part ?. toolName ;
283+
284+ if ( ! ( toolName && typeof toolName === "string" ) ) {
285+ continue ;
286+ }
287+ if ( ! isBuiltInToolName ( toolName ) ) {
288+ continue ;
289+ }
290+
291+ if ( partType === "tool-error" ) {
292+ incrementToolCount ( failedByName , toolName ) ;
293+ continue ;
294+ }
295+
296+ if (
297+ partType === "tool-result" &&
298+ outputIndicatesToolFailure ( part . output )
299+ ) {
300+ incrementToolCount ( failedByName , toolName ) ;
301+ }
302+ }
303+
304+ continue ;
305+ }
306+
307+ for ( const toolResult of step . toolResults ?? [ ] ) {
308+ const toolName = toolResult ?. toolName ;
309+ if ( ! ( toolName && typeof toolName === "string" ) ) {
310+ continue ;
311+ }
312+ if ( ! isBuiltInToolName ( toolName ) ) {
313+ continue ;
314+ }
315+ if ( outputIndicatesToolFailure ( toolResult . output ) ) {
316+ incrementToolCount ( failedByName , toolName ) ;
317+ }
318+ }
319+ }
320+
321+ return failedByName ;
322+ }
323+
324+ export function getChargeableToolCallsByName ( params : {
325+ toolCallsByName : Record < string , number > ;
326+ failedBuiltInToolCallsByName ?: Record < string , number > ;
327+ } ) : Record < string , number > {
328+ const failedByName = params . failedBuiltInToolCallsByName ?? { } ;
329+ const chargeableByName : Record < string , number > = { } ;
330+
331+ for ( const [ toolName , rawCount ] of Object . entries ( params . toolCallsByName ) ) {
332+ if ( ! Number . isFinite ( rawCount ) || rawCount <= 0 ) {
333+ continue ;
334+ }
335+
336+ const attemptedCount = Math . floor ( rawCount ) ;
337+ const failedCount = isBuiltInToolName ( toolName )
338+ ? Math . max (
339+ 0 ,
340+ Math . floor (
341+ Number . isFinite ( failedByName [ toolName ] )
342+ ? ( failedByName [ toolName ] as number )
343+ : 0
344+ )
345+ )
346+ : 0 ;
347+ const chargeableCount = Math . max ( 0 , attemptedCount - failedCount ) ;
348+
349+ if ( chargeableCount > 0 ) {
350+ chargeableByName [ toolName ] = chargeableCount ;
351+ }
352+ }
353+
354+ return chargeableByName ;
355+ }
356+
216357function buildStopConditions ( params : {
217358 toolBudgetCap : number ;
218359 usedNonFinishCallsOffset ?: number ;
@@ -223,7 +364,7 @@ function buildStopConditions(params: {
223364
224365 return [
225366 ...finishToolNames . map ( ( toolName ) => hasToolCall ( toolName ) ) ,
226- ( { steps } : { steps : ToolStepLike [ ] } ) =>
367+ ( { steps } : { steps : readonly ToolStepLike [ ] } ) =>
227368 usedNonFinishCallsOffset + countNonFinishToolCallsFromSteps ( steps ) >=
228369 params . toolBudgetCap ,
229370 stepCountIs ( params . toolBudgetCap + 2 ) ,
@@ -499,7 +640,7 @@ async function generateWithToolLoopAgent(input: {
499640 stopWhen : Array <
500641 | ReturnType < typeof hasToolCall >
501642 | ReturnType < typeof stepCountIs >
502- | ( ( params : { steps : ToolStepLike [ ] } ) => boolean )
643+ | ( ( params : { steps : readonly ToolStepLike [ ] } ) => boolean )
503644 > ;
504645 abortSignal ?: AbortSignal ;
505646} ) : Promise < Awaited < ReturnType < ToolLoopAgent [ "generate" ] > > > {
@@ -618,6 +759,7 @@ export async function generate(
618759 sendPrivateMessage : 0 ,
619760 } ,
620761 toolCallsByName,
762+ chargeableToolCallsByName : toolCallsByName ,
621763 totalToolCalls : getTotalToolCalls ( toolCallsByName ) ,
622764 } ;
623765 }
@@ -717,7 +859,7 @@ export async function generate(
717859 const systemPrompt = buildRuntimeSystemPrompt ( ) ;
718860 const prepareStep : PrepareStepFunction < ToolSet > = ( { steps } ) => {
719861 const nonFinishCallsUsed = countNonFinishToolCallsFromSteps (
720- ( steps as ToolStepLike [ ] | undefined ) ?? [ ]
862+ ( steps as readonly ToolStepLike [ ] | undefined ) ?? [ ]
721863 ) ;
722864 const budgetExhausted = nonFinishCallsUsed >= maxToolInvocationsPerRun ;
723865
@@ -818,6 +960,7 @@ export async function generate(
818960 sendPrivateMessage : toolContext . counters ?. sendPrivateMessage ?? 0 ,
819961 } ,
820962 toolCallsByName,
963+ chargeableToolCallsByName : toolCallsByName ,
821964 totalToolCalls : getTotalToolCalls ( toolCallsByName ) ,
822965 usedCustomSkills : buildUsedCustomSkills ( ) ,
823966 } ;
@@ -839,6 +982,13 @@ export async function generate(
839982 sdkToolCallsByName ,
840983 counterToolCallsByName
841984 ) ;
985+ const failedBuiltInToolCallsByName = buildFailedBuiltInToolCallsByName (
986+ result . steps as readonly ToolStepLike [ ] | undefined
987+ ) ;
988+ const chargeableToolCallsByName = getChargeableToolCallsByName ( {
989+ toolCallsByName,
990+ failedBuiltInToolCallsByName,
991+ } ) ;
842992 const totalToolCalls = getTotalToolCalls ( toolCallsByName ) ;
843993 const nonFinishToolCalls = getNonFinishToolCallCount ( toolCallsByName ) ;
844994 const remainingNonFinishBudget = Math . max (
@@ -893,6 +1043,7 @@ export async function generate(
8931043 sendPrivateMessage : sendPrivateMessageCallCount ,
8941044 } ,
8951045 toolCallsByName,
1046+ chargeableToolCallsByName,
8961047 totalToolCalls,
8971048 usedCustomSkills : buildUsedCustomSkills ( ) ,
8981049 } ;
@@ -920,6 +1071,7 @@ export async function generate(
9201071 sendPrivateMessage : sendPrivateMessageCallCount ,
9211072 } ,
9221073 toolCallsByName,
1074+ chargeableToolCallsByName,
9231075 totalToolCalls,
9241076 usedCustomSkills : buildUsedCustomSkills ( ) ,
9251077 } ;
@@ -965,7 +1117,7 @@ export async function generate(
9651117 tools : repairTools ,
9661118 prepareStep : ( { steps } ) => {
9671119 const repairCallsUsed = countNonFinishToolCallsFromSteps (
968- ( steps as ToolStepLike [ ] | undefined ) ?? [ ]
1120+ ( steps as readonly ToolStepLike [ ] | undefined ) ?? [ ]
9691121 ) ;
9701122 const totalUsed = nonFinishToolCalls + repairCallsUsed ;
9711123 const budgetExhausted = totalUsed >= maxToolInvocationsPerRun ;
@@ -998,6 +1150,11 @@ export async function generate(
9981150 toolCallsByName ,
9991151 repairAbortToolCallsByName
10001152 ) ;
1153+ const combinedRepairAbortChargeableToolCallsByName =
1154+ getChargeableToolCallsByName ( {
1155+ toolCallsByName : combinedRepairAbortToolCallsByName ,
1156+ failedBuiltInToolCallsByName,
1157+ } ) ;
10011158 return {
10021159 decision : {
10031160 action : "skip" as const ,
@@ -1011,6 +1168,8 @@ export async function generate(
10111168 combinedRepairAbortToolCallsByName . sendPrivateMessage ?? 0 ,
10121169 } ,
10131170 toolCallsByName : combinedRepairAbortToolCallsByName ,
1171+ chargeableToolCallsByName :
1172+ combinedRepairAbortChargeableToolCallsByName ,
10141173 totalToolCalls : getTotalToolCalls (
10151174 combinedRepairAbortToolCallsByName
10161175 ) ,
@@ -1031,6 +1190,18 @@ export async function generate(
10311190 repairToolCallsByName ,
10321191 repairCounterToolCallsByName
10331192 ) ;
1193+ const repairFailedBuiltInToolCallsByName =
1194+ buildFailedBuiltInToolCallsByName (
1195+ repairResult . steps as readonly ToolStepLike [ ] | undefined
1196+ ) ;
1197+ const combinedFailedBuiltInToolCallsByName = mergeToolCallsByName (
1198+ failedBuiltInToolCallsByName ,
1199+ repairFailedBuiltInToolCallsByName
1200+ ) ;
1201+ const repairChargeableToolCallsByName = getChargeableToolCallsByName ( {
1202+ toolCallsByName : combinedRepairToolCallsByName ,
1203+ failedBuiltInToolCallsByName : combinedFailedBuiltInToolCallsByName ,
1204+ } ) ;
10341205 const repairTotalToolCalls = getTotalToolCalls (
10351206 combinedRepairToolCallsByName
10361207 ) ;
@@ -1062,6 +1233,7 @@ export async function generate(
10621233 sendPrivateMessage : repairSendPrivateMessageCalls ,
10631234 } ,
10641235 toolCallsByName : combinedRepairToolCallsByName ,
1236+ chargeableToolCallsByName : repairChargeableToolCallsByName ,
10651237 totalToolCalls : repairTotalToolCalls ,
10661238 usedCustomSkills : buildUsedCustomSkills ( ) ,
10671239 } ;
@@ -1089,6 +1261,7 @@ export async function generate(
10891261 sendPrivateMessage : repairSendPrivateMessageCalls ,
10901262 } ,
10911263 toolCallsByName : combinedRepairToolCallsByName ,
1264+ chargeableToolCallsByName : repairChargeableToolCallsByName ,
10921265 totalToolCalls : repairTotalToolCalls ,
10931266 usedCustomSkills : buildUsedCustomSkills ( ) ,
10941267 } ;
@@ -1121,6 +1294,7 @@ export async function generate(
11211294 sendPrivateMessage : sendPrivateMessageCallCount ,
11221295 } ,
11231296 toolCallsByName,
1297+ chargeableToolCallsByName,
11241298 totalToolCalls,
11251299 usedCustomSkills : buildUsedCustomSkills ( ) ,
11261300 } ;
@@ -1159,6 +1333,7 @@ export async function generate(
11591333 sendPrivateMessage : sendPrivateMessageCallCount ,
11601334 } ,
11611335 toolCallsByName,
1336+ chargeableToolCallsByName,
11621337 totalToolCalls,
11631338 usedCustomSkills : buildUsedCustomSkills ( ) ,
11641339 } ;
0 commit comments