From 85753fa7d6a2ba575c49e7ad163e93847118329c Mon Sep 17 00:00:00 2001 From: Omar Alaaeldein Date: Fri, 3 Jul 2026 18:55:07 -0400 Subject: [PATCH] fix: resolve cyclic structure serialization crash in token counter When the token count API call fails or is not available, the CLI fallback logic serializes using . However, contains raw Zod schema objects (), which have circular references, causing a TypeError. Changes: - Added a utility to strip circular references from objects. - Sanitize the inside at creation time using so it is a plain, serializable object. This also prevents similar serialization failures when saving run state to disk. --- packages/agent-runtime/src/run-agent-step.ts | 4 +-- .../agent-runtime/src/util/token-counter.ts | 26 ++++++++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) 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 {