diff --git a/packages/agent-runtime/src/run-agent-step.ts b/packages/agent-runtime/src/run-agent-step.ts index d45b5ba8ff..54895a6aa4 100644 --- a/packages/agent-runtime/src/run-agent-step.ts +++ b/packages/agent-runtime/src/run-agent-step.ts @@ -45,7 +45,7 @@ import { buildUserMessageContent, expireMessages, } from './util/messages' -import { countTokensJson } from './util/token-counter' +import { countTokensJson, safeJsonStringify } from './util/token-counter' import type { AgentTemplate } from '@codebuff/common/types/agent-template' import type { TrackEventFn } from '@codebuff/common/types/contracts/analytics' @@ -860,7 +860,7 @@ export async function loopAgentSteps( // Convert tools to a serializable format for context-pruner token counting const toolDefinitions = mapValues(tools, (tool) => ({ description: tool.description, - inputSchema: tool.inputSchema as {}, + inputSchema: JSON.parse(safeJsonStringify(tool.inputSchema)), })) const additionalToolDefinitionsWithCache = async () => { diff --git a/packages/agent-runtime/src/util/token-counter.ts b/packages/agent-runtime/src/util/token-counter.ts index 960a676cd3..1db50d5c74 100644 --- a/packages/agent-runtime/src/util/token-counter.ts +++ b/packages/agent-runtime/src/util/token-counter.ts @@ -27,10 +27,34 @@ export function countTokens(text: string): number { } } +export function safeJsonStringify(obj: any): string { + const seen = new WeakSet() + return JSON.stringify(obj, (key, value) => { + if (typeof value === 'object' && value !== null) { + if (seen.has(value)) { + return '[Circular]' + } + seen.add(value) + } + if (typeof value === 'function') { + return value.toString() + } + return value + }) +} + export function countTokensJson(text: string | object): number { - return countTokens(JSON.stringify(text)) + if (typeof text === 'string') { + return countTokens(text) + } + try { + return countTokens(JSON.stringify(text)) + } catch (e) { + return countTokens(safeJsonStringify(text)) + } } + export function countTokensForFiles( files: Record, ): Record {