diff --git a/packages/node-runtime/src/ai/agent/core.ts b/packages/node-runtime/src/ai/agent/core.ts index e9fa477e..e6dbd702 100644 --- a/packages/node-runtime/src/ai/agent/core.ts +++ b/packages/node-runtime/src/ai/agent/core.ts @@ -123,14 +123,6 @@ export async function runAgentCore(options: AgentCoreOptions): Promise { return raw as Record } +function normalizeRows(raw: unknown, maxRows: number): { rows: Record[]; truncated: boolean } { + if (!Array.isArray(raw)) throw new Error('rows must be an array') + const items = raw.filter( + (item): item is Record => typeof item === 'object' && item !== null && !Array.isArray(item) + ) + const truncated = items.length > maxRows + return { rows: truncated ? items.slice(0, maxRows) : items, truncated } +} + function normalizeMaxRows(raw: unknown): number { const value = typeof raw === 'number' ? raw : DEFAULT_MAX_ROWS if (!Number.isFinite(value)) return DEFAULT_MAX_ROWS @@ -103,13 +119,26 @@ function summarizeChartForModel( async function handler(params: Record, context: ToolExecutionContext): Promise { if (!context.dataProvider) throw new Error('render_chart requires a data provider') + const hasSql = typeof params.sql === 'string' && params.sql.trim().length > 0 + const hasRows = Array.isArray(params.rows) + if (!hasSql && !hasRows) throw new Error('render_chart requires either sql or rows') + const maxRows = normalizeMaxRows(params.maxRows) - const sql = normalizeSql(params.sql, maxRows) - const sqlParams = normalizeParams(params.params) + let rows: Record[] + let truncated: boolean + + if (hasRows) { + const normalized = normalizeRows(params.rows, maxRows) + rows = normalized.rows + truncated = normalized.truncated + } else { + const sql = normalizeSql(params.sql, maxRows) + const sqlParams = normalizeParams(params.params) + const fetchedRows = await context.dataProvider.executeParameterizedSql>(sql, sqlParams) + truncated = fetchedRows.length > maxRows + rows = truncated ? fetchedRows.slice(0, maxRows) : fetchedRows + } - const fetchedRows = await context.dataProvider.executeParameterizedSql>(sql, sqlParams) - const truncated = fetchedRows.length > maxRows - const rows = truncated ? fetchedRows.slice(0, maxRows) : fetchedRows const chart = buildChartPayload(rows, params.chartSpec, { truncated }) return { @@ -127,7 +156,7 @@ async function handler(params: Record, context: ToolExecutionCo export const renderChartTool: ToolDefinition = { name: 'render_chart', description: - 'Generate a native ChatLab chart from read-only SQL plus ChartSpec v1. Use this for flexible bar, line, pie, and heatmap charts. Never output HTML, JavaScript, SVG, ECharts options, or rendering code.', + "Generate a native ChatLab chart from ChartSpec v1. Provide either `rows` (pre-fetched data array from a tool result such as get_time_stats or member_stats) or `sql` (read-only SELECT). Prefer `rows` when data is already available — pass the tool's data array directly. Use `sql` only for custom aggregations not covered by existing tools. Never output HTML, JavaScript, SVG, ECharts options, or rendering code.", inputSchema, handler, category: 'analysis',