({ title, columns, rows }, { thread })
| 98 | Math.max( |
| 99 | (header[c] ?? "").length, |
| 100 | ...body.map((row) => (row[c] ?? "").length), |
| 101 | ), |
| 102 | ); |
| 103 | const fmt = (row: string[]) => |
| 104 | "| " + |
| 105 | cols |
| 106 | .map((col, c) => { |
| 107 | const cell = row[c] ?? ""; |
| 108 | const width = widths[c] ?? 0; |
| 109 | if (col.align === "right") return cell.padStart(width); |
| 110 | if (col.align === "center") { |
| 111 | const total = Math.max(width - cell.length, 0); |
| 112 | const left = Math.floor(total / 2); |
| 113 | const right = total - left; |
| 114 | return " ".repeat(left) + cell + " ".repeat(right); |
| 115 | } |
| 116 | return cell.padEnd(width); |
| 117 | }) |
| 118 | .join(" | ") + |
| 119 | " |"; |
| 120 | return "```\n" + [fmt(header), ...body.map(fmt)].join("\n") + "\n```"; |
| 121 | } |
| 122 | |
| 123 | export const renderTableTool = defineChannelTool({ |
| 124 | name: "render_table", |
| 125 | description: |
| 126 | "Render tabular data as a table posted to the conversation thread. Pass " + |
| 127 | "columns (each with a header and optional alignment) and rows (arrays of " + |
| 128 | "cell values in column order). Use for 'show as a table' — issue lists " + |
| 129 | "with several fields, metrics from a CSV, comparisons — when a chart " + |
| 130 | "isn't the right shape. Max 20 columns and 99 rows.", |
| 131 | parameters: schema, |
| 132 | async handler({ title, columns, rows }, { thread }) { |
| 133 | const { cols, dataRows, notes } = clamp(columns, rows); |
| 134 | // When rows/columns were truncated, surface it both to the user (a |
| 135 | // trailing <Context> note under the table) and to the agent (appended to |
| 136 | // the returned status string), so a silent drop never happens. |
| 137 | const noteBlock = notes.length > 0 ? <Context>{notes.join("; ")}</Context> : null; |
| 138 | const noteSuffix = notes.length > 0 ? ` (${notes.join("; ")})` : ""; |
nothing calls this directly
no test coverage detected